Akankah kodenya
int a = ((1 + 2) + 3); // Easy to read
jalankan lebih lambat dari
int a = 1 + 2 + 3; // (Barely) Not quite so easy to read
atau kompiler modern cukup pintar untuk menghapus / mengoptimalkan tanda kurung "tidak berguna".
Ini mungkin tampak seperti masalah optimisasi yang sangat kecil, tetapi memilih C ++ daripada C # / Java / ... adalah semua tentang optimasi (IMHO).
c++
optimization
compilation
Serge
sumber
sumber
Jawaban:
Kompiler sebenarnya tidak pernah menyisipkan atau menghapus tanda kurung; itu hanya membuat pohon parse (di mana tidak ada tanda kurung) yang sesuai dengan ekspresi Anda, dan dalam melakukannya harus menghormati tanda kurung yang Anda tulis. Jika Anda sepenuhnya mengurung ekspresi Anda maka itu akan segera menjadi jelas bagi pembaca manusia apa pohon parse itu; jika Anda pergi ke ekstrim menempatkan kurung terang-terangan berlebihan seperti dalam
int a = (((0)));
maka Anda akan menyebabkan beberapa tekanan yang tidak berguna pada neuron pembaca sementara juga membuang-buang beberapa siklus di pengurai, namun mengubah pohon parsing yang dihasilkan (dan karena itu kode yang dihasilkan ) sedikit pun.Jika Anda tidak menulis tanda kurung, maka parser masih harus melakukan tugasnya membuat pohon parse, dan aturan untuk operator diutamakan dan asosiatif mengatakan dengan tepat pohon parse mana yang harus dibangun. Anda dapat mempertimbangkan aturan-aturan itu sebagai memberi tahu kompiler yang mana (implisit) tanda kurung harus dimasukkan ke dalam kode Anda, meskipun parser sebenarnya tidak pernah berurusan dengan tanda kurung dalam kasus ini: itu hanya telah dibangun untuk menghasilkan pohon parse yang sama seolah-olah tanda kurung hadir di tempat-tempat tertentu. Jika Anda menempatkan tanda kurung di tempat-tempat itu, seperti
int a = (1+2)+3;
(asosiatifitas+
adalah di sebelah kiri) maka parser akan tiba pada hasil yang sama dengan rute yang sedikit berbeda. Jika Anda memasukkan tanda kurung yang berbeda seperti dalamint a = 1+(2+3);
maka Anda memaksa parse tree yang berbeda, yang mungkin akan menyebabkan kode yang berbeda untuk dihasilkan (meskipun mungkin tidak, karena kompiler dapat menerapkan transformasi setelah membangun parse tree, selama efek mengeksekusi kode yang dihasilkan tidak akan pernah berbeda untuk Itu). Andaikata ada perbedaan dalam kode resuting, tidak ada yang secara umum dapat dikatakan lebih efisien; titik yang paling penting tentu saja adalah bahwa sebagian besar pohon parse tidak memberikan ekspresi yang setara secara matematis, jadi membandingkan kecepatan eksekusi mereka di samping intinya: orang harus menulis ekspresi yang memberikan hasil yang tepat.Jadi hasilnya adalah: gunakan tanda kurung sesuai kebutuhan untuk kebenaran, dan sesuai keinginan untuk keterbacaan; jika berlebihan mereka tidak memiliki efek sama sekali pada kecepatan eksekusi (dan efek yang dapat diabaikan pada waktu kompilasi).
Dan semua ini tidak ada hubungannya dengan optimasi , yang muncul setelah pohon parse dibangun, sehingga tidak dapat mengetahui bagaimana pohon parse dibangun. Ini berlaku tanpa perubahan dari kompiler yang paling tua dan paling bodoh ke yang paling pintar dan paling modern. Hanya dalam bahasa yang diinterpretasikan (di mana "waktu kompilasi" dan "waktu eksekusi" adalah kebetulan) mungkin ada hukuman untuk kurung yang berlebihan, tetapi meskipun demikian saya pikir sebagian besar bahasa tersebut diatur sehingga setidaknya fase parsing dilakukan hanya sekali untuk setiap pernyataan (menyimpan beberapa bentuk yang sudah diurai untuk dieksekusi).
sumber
a = b + c * d;
,a = b + (c * d);
akan menjadi kurung redundan [tidak berbahaya]. Jika mereka membantu Anda membuat kode lebih mudah dibaca, baiklah.a = (b + c) * d;
akan menjadi tanda kurung non-berlebihan - mereka benar-benar mengubah pohon parse yang dihasilkan dan memberikan hasil yang berbeda. Sangat legal untuk dilakukan (pada kenyataannya, perlu), tetapi mereka tidak sama dengan pengelompokan default tersirat.Tanda kurung ada di sana hanya untuk keuntungan Anda - bukan kompiler. Kompiler akan membuat kode mesin yang benar untuk mewakili pernyataan Anda.
FYI, kompiler cukup pintar untuk mengoptimalkannya sepenuhnya jika bisa. Dalam contoh Anda, ini akan berubah menjadi
int a = 6;
pada waktu kompilasi.sumber
int a = 8;// = 2*3 + 5
int five = 7; //HR made us change this to six...
Jawaban untuk pertanyaan yang sebenarnya Anda tanyakan adalah tidak, tetapi jawaban untuk pertanyaan yang ingin Anda tanyakan adalah ya. Menambahkan tanda kurung tidak memperlambat kode.
Anda mengajukan pertanyaan tentang pengoptimalan, tetapi kurung tidak ada hubungannya dengan pengoptimalan. Kompiler menerapkan berbagai teknik pengoptimalan dengan tujuan meningkatkan ukuran atau kecepatan kode yang dihasilkan (kadang-kadang keduanya). Misalnya, mungkin mengambil ekspresi A ^ 2 (A kuadrat) dan menggantinya dengan A x A (A dikalikan dengan sendirinya) jika itu lebih cepat. Jawabannya di sini adalah tidak, kompiler tidak melakukan apa pun yang berbeda dalam fase optimisasi tergantung pada apakah tanda kurung hadir tidak.
Saya pikir Anda bermaksud bertanya apakah kompiler masih menghasilkan kode yang sama jika Anda menambahkan tanda kurung yang tidak perlu ke ekspresi, di tempat yang menurut Anda dapat meningkatkan keterbacaan. Dengan kata lain, jika Anda menambahkan tanda kurung adalah kompiler yang cukup pintar untuk mengeluarkannya kembali daripada menghasilkan kode yang lebih buruk. Jawabannya adalah ya, selalu.
Biarkan saya mengatakannya dengan hati-hati. Jika Anda menambahkan tanda kurung ke ekspresi yang benar-benar tidak perlu (tidak memiliki efek apa pun pada arti atau urutan evaluasi ekspresi), kompiler akan secara diam-diam membuangnya dan menghasilkan kode yang sama.
Namun, ada ekspresi tertentu di mana kurung yang tampaknya tidak perlu benar-benar akan mengubah urutan evaluasi ekspresi dan dalam hal itu kompiler akan menghasilkan kode untuk memberlakukan apa yang sebenarnya Anda tulis, yang mungkin berbeda dari yang Anda inginkan. Berikut ini sebuah contoh. Jangan lakukan ini!
Jadi, tambahkan tanda kurung jika Anda mau, tetapi pastikan itu benar-benar tidak perlu!
Saya tidak pernah melakukan. Ada risiko kesalahan tanpa manfaat nyata.
Catatan kaki: perilaku unsigned terjadi ketika ekspresi integer yang ditandatangani mengevaluasi nilai yang berada di luar rentang yang dapat diekspresikan, dalam hal ini -32767 hingga +32767. Ini adalah topik yang kompleks, di luar jangkauan untuk jawaban ini.
sumber
a
benar-benar tidak ditandatangani, memimpin perhitungan Anda dengan-a + b
mudah bisa meluap juga, jikaa
negatif danb
positif.(b+c)
di baris terakhir akan mempromosikan argumennyaint
, jadi kecuali jika compiler mendefinisikanint
menjadi 16 bit (baik karena itu kuno atau menargetkan mikrokontroler kecil) baris terakhir akan sangat sah.int
dipromosikanint
kecuali tipe itu tidak dapat mewakili semua nilainya dalam hal mana ia dipromosikanunsigned int
. Penyusun dapat mengabaikan promosi jika semua perilaku yang ditetapkan akan sama seperti jika promosi dimasukkan . Pada mesin di mana tipe 16-bit berperilaku sebagai cincin aljabar abstrak pembungkus, (a + b) + c dan + (b + c) akan sama. Namun, jikaint
tipe 16-bit yang terjebak pada overflow, maka akan ada kasus di mana salah satu ekspresi ...Kurung hanya ada agar Anda dapat memanipulasi urutan prioritas operator. Setelah dikompilasi, tanda kurung tidak ada lagi karena run-time tidak membutuhkannya. Proses kompilasi menghilangkan semua tanda kurung, spasi dan gula sintaksis lain yang Anda dan saya butuhkan dan mengubah semua operator menjadi sesuatu yang [jauh] lebih sederhana bagi komputer untuk dieksekusi.
Jadi, di mana Anda dan saya mungkin melihat ...
... kompiler mungkin memancarkan sesuatu yang lebih seperti ini:
Program dijalankan dengan mulai dari awal dan mengeksekusi setiap instruksi secara bergantian.
Prioritas operator sekarang "pertama datang, pertama dilayani".
Semuanya diketik dengan kuat, karena kompiler mengerjakan semua itu saat sedang merobek sintaksis aslinya.
OK, tidak seperti barang-barang yang Anda dan saya tangani, tapi kemudian kita tidak menjalankannya!
sumber
Tergantung apakah itu floating point atau tidak:
Dalam penambahan aritmatika floating point tidak asosiatif sehingga optimizer tidak dapat menyusun ulang operasi (kecuali jika Anda menambahkan saklar kompiler fastmath).
Dalam operasi integer, mereka dapat disusun ulang.
Dalam contoh Anda, keduanya akan menjalankan waktu yang sama persis karena keduanya akan dikompilasi dengan kode yang sama persis (penambahan dievaluasi dari kiri ke kanan).
Namun bahkan java dan C # akan dapat mengoptimalkannya, mereka hanya akan melakukannya saat runtime.
sumber
Kompiler C ++ khas diterjemahkan ke kode mesin, bukan C ++ itu sendiri . Itu menghilangkan parens yang tidak berguna, ya, karena pada saat itu selesai, tidak ada parens sama sekali. Kode mesin tidak berfungsi seperti itu.
sumber
Kedua kode berakhir dengan kode keras 6:
Periksa sendiri di sini
sumber
Tidak, tapi ya, tapi mungkin, tapi mungkin sebaliknya, tapi tidak.
Seperti yang telah ditunjukkan orang, (dengan asumsi bahasa di mana penambahan adalah asosiatif-kiri, seperti C, C ++, C # atau Java) ekspresi
((1 + 2) + 3)
tersebut persis sama dengan1 + 2 + 3
. Mereka berbeda cara menulis sesuatu dalam kode sumber, yang akan memiliki efek nol pada kode mesin atau kode byte yang dihasilkan.Apa pun hasilnya, ini akan menjadi instruksi untuk mis. Menambah dua register dan kemudian menambahkan yang ketiga, atau mengambil dua nilai dari tumpukan, menambahkannya, mendorongnya kembali, lalu mengambilnya dan yang lain dan menambahkannya, atau menambahkan tiga register di operasi tunggal, atau cara lain untuk menjumlahkan tiga angka tergantung pada apa yang paling masuk akal di tingkat berikutnya (kode mesin atau kode byte). Dalam kasus kode byte, yang pada gilirannya kemungkinan akan mengalami penataan ulang yang serupa di dalam hal itu misalnya IL yang setara dengan ini (yang akan menjadi serangkaian beban ke tumpukan, dan pasangan popping untuk menambah dan kemudian mendorong kembali hasilnya) tidak akan menghasilkan salinan langsung dari logika itu di tingkat kode mesin, tetapi sesuatu yang lebih masuk akal untuk mesin yang bersangkutan.
Tetapi ada sesuatu yang lebih dari pertanyaan Anda.
Dalam hal setiap kompiler C, C ++, Java, atau C # yang waras, saya mengharapkan hasil dari kedua pernyataan yang Anda berikan memiliki hasil yang sama persis seperti:
Mengapa kode yang dihasilkan harus membuang waktu mengerjakan matematika pada literal? Tidak ada perubahan pada keadaan program akan berhenti hasilnya dari
1 + 2 + 3
menjadi6
, sehingga ini apa yang harus pergi dalam kode dieksekusi. Memang, mungkin bahkan tidak itu (tergantung pada apa yang Anda lakukan dengan 6 itu, mungkin kita bisa membuang semuanya; dan bahkan C # dengan filosofi itu "jangan mengoptimalkan, karena jitter akan mengoptimalkan ini pula" akan menghasilkan samaint a = 6
atau hanya membuang semuanya sebagai tidak perlu).Namun ini menuntun kami ke kemungkinan perpanjangan pertanyaan Anda. Pertimbangkan yang berikut ini:
dan
(Catatan, dua contoh terakhir ini tidak valid C #, sedangkan yang lainnya di sini adalah, dan mereka valid dalam C, C ++ dan Java.)
Di sini sekali lagi kita memiliki kode yang sama persis dalam hal output. Karena mereka bukan ekspresi konstan, mereka tidak akan dihitung pada waktu kompilasi. Mungkin satu bentuk lebih cepat dari yang lain. Mana yang lebih cepat? Itu akan tergantung pada prosesor dan mungkin pada beberapa perbedaan yang agak sewenang-wenang dalam keadaan (terutama karena jika seseorang lebih cepat, itu tidak akan jauh lebih cepat).
Dan mereka tidak sepenuhnya tidak terkait dengan pertanyaan Anda, karena kebanyakan tentang perbedaan dalam urutan di mana sesuatu dilakukan secara konseptual .
Di masing-masing dari mereka, ada alasan untuk curiga bahwa satu mungkin lebih cepat daripada yang lain. Dekremen tunggal mungkin memiliki instruksi khusus, jadi
(b / 2)--
memang bisa lebih cepat daripada(b - 2) / 2
.d * 32
mungkin bisa diproduksi lebih cepat dengan mengubahnyad << 5
jadi membuatd * 32 - d
lebih cepat daripadad * 31
. Perbedaan antara dua yang terakhir sangat menarik; satu memungkinkan beberapa pemrosesan dilewati dalam beberapa kasus, tetapi yang lain menghindari kemungkinan salah prediksi cabang.Jadi, ini meninggalkan kita dengan dua pertanyaan: 1. Apakah satu sebenarnya lebih cepat dari yang lain? 2. Apakah kompiler akan mengkonversi lebih lambat menjadi lebih cepat?
Dan jawabannya adalah 1. Tergantung. 2. Mungkin.
Atau untuk memperluas, itu tergantung karena itu tergantung pada prosesor yang bersangkutan. Tentu saja ada prosesor di mana setara kode mesin naif dari satu akan lebih cepat daripada kode mesin naif yang setara dengan yang lain. Selama sejarah komputasi elektronik, tidak ada satu yang selalu lebih cepat, baik (elemen mis-prediksi cabang khususnya tidak relevan bagi banyak ketika CPU non-pipa lebih umum).
Dan mungkin, karena ada banyak optimisasi berbeda yang dilakukan oleh kompiler (dan kegugupan, dan mesin skrip), dan sementara beberapa mungkin diamanatkan dalam kasus-kasus tertentu, umumnya kita akan dapat menemukan beberapa bagian dari kode yang secara logis setara yang bahkan kompiler yang paling naif memiliki hasil yang persis sama dan beberapa bagian dari kode yang setara secara logis di mana bahkan yang paling canggih menghasilkan kode yang lebih cepat untuk satu daripada yang lain (bahkan jika kita harus menulis sesuatu yang benar-benar patologis hanya untuk membuktikan maksud kita).
Tidak. Bahkan dengan perbedaan yang lebih rumit daripada yang saya berikan di sini, sepertinya masalah kecil sekali yang tidak ada hubungannya dengan optimasi. Jika ada, itu masalah pesimisasi karena Anda curiga semakin sulit dibaca
((1 + 2) + 3
bisa lebih lambat daripada lebih mudah dibaca1 + 2 + 3
.Jika itu yang benar-benar memilih C ++ daripada C # atau Java adalah "semua tentang" Saya akan mengatakan orang harus membakar salinan Stroustrup dan ISO / IEC 14882 mereka dan membebaskan ruang kompiler C ++ mereka untuk meninggalkan ruang untuk lebih banyak MP3 atau sesuatu.
Bahasa-bahasa ini memiliki keunggulan yang berbeda satu sama lain.
Salah satunya adalah bahwa C ++ umumnya masih lebih cepat dan lebih ringan pada penggunaan memori. Ya, ada contoh di mana C # dan / atau Java lebih cepat dan / atau memiliki penggunaan memori aplikasi yang lebih baik seumur hidup, dan ini menjadi lebih umum karena teknologi yang terlibat meningkat, tetapi kita masih bisa mengharapkan program rata-rata yang ditulis dalam C ++ menjadi dieksekusi lebih kecil yang melakukan tugasnya lebih cepat dan menggunakan lebih sedikit memori daripada yang setara di salah satu dari kedua bahasa.
Ini bukan optimasi.
Optimalisasi kadang-kadang berarti "membuat segalanya berjalan lebih cepat". Ini bisa dimengerti, karena sering kali ketika kita benar-benar berbicara tentang "optimasi" kita memang berbicara tentang membuat segalanya berjalan lebih cepat, dan karena itu seseorang telah menjadi singkatan untuk yang lain dan saya akui saya menyalahgunakan kata itu dengan cara saya sendiri.
Kata yang benar untuk "membuat segalanya berjalan lebih cepat" bukanlah pengoptimalan . Kata yang benar di sini adalah peningkatan . Jika Anda membuat perubahan pada suatu program dan satu-satunya perbedaan yang berarti adalah sekarang lebih cepat, tidak dioptimalkan dengan cara apa pun, itu hanya lebih baik.
Optimalisasi adalah ketika kami melakukan peningkatan dalam hal aspek tertentu dan / atau kasus tertentu. Contoh umum adalah:
Kasus-kasus seperti itu akan dibenarkan jika, misalnya:
Tapi, kasus-kasus seperti itu juga tidak akan dibenarkan dalam skenario lain: Kode belum dibuat lebih baik oleh ukuran kualitas mutlak mutlak, itu dibuat lebih baik dalam hal tertentu yang membuatnya lebih cocok untuk penggunaan tertentu; dioptimalkan.
Dan pilihan bahasa memang berpengaruh di sini, karena kecepatan, penggunaan memori, dan keterbacaan semua dapat dipengaruhi olehnya, tetapi juga kompatibilitas dengan sistem lain, ketersediaan perpustakaan, ketersediaan runtime, kematangan runtime tersebut pada sistem operasi yang diberikan (untuk dosa-dosa saya, saya entah bagaimana akhirnya memiliki Linux dan Android sebagai OS favorit saya dan C # sebagai bahasa favorit saya, dan sementara Mono hebat, tetapi saya masih menghadapi yang ini sedikit).
Mengatakan "memilih C ++ daripada C # / Java / ... adalah semua tentang optimasi" hanya masuk akal jika Anda berpikir C ++ benar-benar menyebalkan, karena optimasi adalah tentang "lebih baik meskipun ..." tidak "lebih baik". Jika Anda berpikir C ++ lebih baik dari itu sendiri, maka hal terakhir yang Anda butuhkan adalah khawatir tentang kemungkinan kecil memilih mikro-opts. Memang, Anda mungkin lebih baik meninggalkannya sama sekali; hacker senang adalah kualitas untuk dioptimalkan juga!
Namun, jika Anda cenderung mengatakan "Saya suka C ++, dan salah satu hal yang saya sukai adalah memeras siklus tambahan", maka itu masalah yang berbeda. Itu masih merupakan kasus bahwa mikro-opts hanya layak jika mereka dapat menjadi kebiasaan refleksif (yaitu, cara Anda cenderung untuk kode secara alami akan lebih cepat lebih sering daripada lambat). Kalau tidak, mereka bahkan bukan optimasi prematur, mereka pesimis prematur yang hanya memperburuk keadaan.
sumber
Tanda kurung ada di sana untuk memberi tahu kompiler di mana ekspresi urutan harus dievaluasi. Kadang-kadang mereka tidak berguna (kecuali mereka meningkatkan atau memperburuk keterbacaan), karena mereka menentukan urutan yang akan digunakan. Terkadang mereka mengubah urutan. Di
praktis setiap bahasa yang ada memiliki aturan bahwa jumlah dievaluasi dengan menambahkan 1 + 2, kemudian menambahkan hasilnya ditambah 3. Jika Anda menulis
maka tanda kurung akan memaksakan urutan yang berbeda: Pertama menambahkan 2 + 3, kemudian menambahkan 1 ditambah hasilnya. Contoh tanda kurung Anda menghasilkan urutan yang sama dengan yang seharusnya dihasilkan. Sekarang dalam contoh ini, urutan operasi sedikit berbeda, tetapi cara penambahan integer bekerja, hasilnya sama. Di
tanda kurung sangat penting; meninggalkan mereka akan mengubah hasilnya dari 9 menjadi 1.
Setelah kompiler menentukan operasi apa yang dilakukan dalam urutan apa, kurung benar-benar dilupakan. Semua yang diingat oleh kompiler pada titik ini adalah operasi mana yang harus dijalankan sesuai urutan. Jadi sebenarnya tidak ada yang bisa dioptimalkan oleh kompiler di sini, tanda kurung hilang .
sumber
practically every language in existence
; kecuali APL: Coba (di sini) [tryapl.org] memasukkan(1-2)+3
(2),1-(2+3)
(-4) dan1-2+3
(juga -4).Saya setuju dengan banyak dari apa yang telah dikatakan, namun ... lengkungan berlebihan di sini adalah bahwa tanda kurung ada untuk memaksa urutan operasi ... yang benar-benar dilakukan oleh kompiler. Ya, itu menghasilkan kode mesin ... tapi, bukan itu intinya dan bukan itu yang ditanyakan.
Tanda kurung memang hilang: seperti telah dikatakan, mereka bukan bagian dari kode mesin, yang merupakan angka dan bukan hal lain. Kode assembly bukan kode mesin, ini dapat dibaca semi-manusia dan berisi instruksi berdasarkan nama - bukan opcode. Mesin menjalankan apa yang disebut opcodes - representasi numerik dari bahasa assembly.
Bahasa seperti Jawa jatuh ke dalam area di antara keduanya karena mereka hanya mengkompilasi sebagian pada mesin yang memproduksinya. Mereka dikompilasi ke mesin kode spesifik pada mesin yang menjalankannya, tetapi itu tidak membuat perbedaan untuk pertanyaan ini - tanda kurung masih hilang setelah kompilasi pertama.
sumber
a = f() + (g() + h());
, compiler gratis untuk meneleponf
,g
danh
agar (atau dalam urutan apapun itu menyenangkan).Compiler, terlepas dari bahasa, menerjemahkan semua matematika infiks ke postfix. Dengan kata lain, ketika kompiler melihat sesuatu seperti:
menerjemahkannya menjadi ini:
Hal ini dilakukan karena sementara notasi infiks lebih mudah bagi orang untuk membaca, notasi postfix jauh lebih dekat dengan langkah aktual yang harus diambil komputer untuk menyelesaikan pekerjaan (dan karena sudah ada algoritma yang dikembangkan dengan baik untuk itu.) definisi, postfix telah menghilangkan semua masalah dengan urutan operasi atau tanda kurung, yang secara alami membuat segalanya lebih mudah ketika benar-benar menulis kode mesin.
Saya merekomendasikan artikel wikipedia tentang Notasi Polandia Mundur untuk informasi lebih lanjut tentang subjek ini.
sumber