Kami memiliki pertanyaan apakah ada perbedaan kinerja antara i++
dan ++i
di C ?
Apa jawaban untuk C ++?
c++
performance
oop
post-increment
pre-increment
Mark Harrison
sumber
sumber
Jawaban:
[Ringkasan Eksekutif: Gunakan
++i
jika Anda tidak memiliki alasan khusus untuk digunakani++
.]Untuk C ++, jawabannya sedikit lebih rumit.
Jika
i
adalah tipe sederhana (bukan turunan dari kelas C ++), maka jawaban yang diberikan untuk C ("Tidak ada perbedaan kinerja") berlaku, karena kompiler menghasilkan kode.Namun, jika
i
adalah turunan dari kelas C ++, makai++
dan++i
membuat panggilan ke salah satuoperator++
fungsi. Berikut ini pasangan standar dari fungsi-fungsi ini:Karena kompiler tidak menghasilkan kode, tetapi hanya memanggil
operator++
fungsi, tidak ada cara untuk mengoptimalkantmp
variabel dan penyalin salinan yang terkait. Jika pembuat salinan mahal, maka ini dapat memiliki dampak kinerja yang signifikan.sumber
Iya. Ada.
Operator ++ mungkin atau mungkin tidak didefinisikan sebagai suatu fungsi. Untuk tipe primitif (int, dobel, ...) operator sudah terpasang, sehingga kompiler mungkin akan dapat mengoptimalkan kode Anda. Tetapi dalam kasus objek yang mendefinisikan operator ++ hal-hal berbeda.
Fungsi operator ++ (int) harus membuat salinan. Itu karena postfix ++ diharapkan mengembalikan nilai yang berbeda dari yang dipegangnya: postfix ++ harus menyimpan nilainya dalam variabel temp, menambah nilainya dan mengembalikan temp. Dalam kasus operator ++ (), awalan ++, tidak perlu membuat salinan: objek dapat bertambah dengan sendirinya dan kemudian kembali sendiri.
Inilah ilustrasi pokoknya:
Setiap kali Anda memanggil operator ++ (int) Anda harus membuat salinan, dan kompiler tidak dapat melakukan apa-apa. Saat diberi pilihan, gunakan operator ++ (); dengan cara ini Anda tidak menyimpan salinan. Mungkin signifikan dalam kasus banyak kenaikan (loop besar?) Dan / atau objek besar.
sumber
C t(*this); ++(*this); return t;
Di baris kedua, Anda menambah pointer ini dengan benar, jadi bagaimana carat
diperbarui jika Anda menambah ini. Bukankah nilai-nilai ini sudah disalint
?The operator++(int) function must create a copy.
Tidak, bukan itu. Tidak lebih dari salinanoperator++()
Berikut ini adalah patokan untuk kasus ketika operator tambahan berada di unit terjemahan berbeda Kompiler dengan g ++ 4.5.
Abaikan masalah gaya untuk saat ini
O (n) kenaikan
Uji
Hasil
Hasil (timing dalam detik) dengan g ++ 4.5 pada mesin virtual:
O (1) kenaikan
Uji
Sekarang mari kita ambil file berikut:
Itu tidak melakukan apa-apa dalam penambahan. Ini mensimulasikan kasus ketika peningkatan memiliki kompleksitas konstan.
Hasil
Hasil sekarang sangat bervariasi:
Kesimpulan
Dari segi kinerja
Jika Anda tidak membutuhkan nilai sebelumnya, biasakan untuk menggunakan pra-kenaikan. Konsisten bahkan dengan tipe builtin, Anda akan terbiasa dan tidak berisiko kehilangan kinerja yang tidak perlu jika Anda pernah mengganti tipe builtin dengan tipe kustom.
Semantik-bijaksana
i++
kataincrement i, I am interested in the previous value, though
.++i
kataincrement i, I am interested in the current value
atauincrement i, no interest in the previous value
. Sekali lagi, Anda akan terbiasa, bahkan jika Anda tidak sekarang.Knuth.
Optimalisasi prematur adalah akar dari semua kejahatan. Seperti pesimisasi dini.
sumber
for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }
, tidak peduli struktur pohon yang sebenarnya (BSP, kd, Quadtree, Octree Grid, dll.). Sebuah seperti iterator akan perlu untuk mempertahankan beberapa negara, misalnyaparent node
,child node
,index
dan hal-hal seperti itu. Secara keseluruhan, sikap saya adalah, bahkan jika hanya ada beberapa contoh, ...Tidak sepenuhnya benar untuk mengatakan bahwa kompiler tidak dapat mengoptimalkan salinan variabel sementara dalam kasus postfix. Tes cepat dengan VC menunjukkan bahwa itu, setidaknya, dapat melakukannya dalam kasus-kasus tertentu.
Dalam contoh berikut, kode yang dihasilkan identik untuk awalan dan postfix, misalnya:
Apakah Anda melakukan ++ testFoo atau testFoo ++, Anda akan tetap mendapatkan kode hasil yang sama. Bahkan, tanpa membaca hitungan dari pengguna, pengoptimal membuat semuanya menjadi konstan. Jadi ini:
Menghasilkan sebagai berikut:
Jadi, walaupun tentu saja versi postfix bisa lebih lambat, mungkin saja pengoptimal akan cukup baik untuk menyingkirkan salinan sementara jika Anda tidak menggunakannya.
sumber
The Google C ++ Style Guide mengatakan:
sumber
Saya ingin menunjukkan posting yang sangat baik oleh Andrew Koenig pada Code Talk baru-baru ini.
http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29
Di perusahaan kami juga kami menggunakan konvensi ++ iter untuk konsistensi dan kinerja di mana berlaku. Namun Andrew memunculkan detail yang terlalu banyak terkait dengan niat vs kinerja. Ada kalanya kita ingin menggunakan iter ++ daripada ++ iter.
Jadi, pertama-tama tentukan niat Anda dan jika pra atau posting tidak masalah, maka pergi dengan pra karena akan memiliki beberapa manfaat kinerja dengan menghindari penciptaan objek tambahan dan melemparkannya.
sumber
@Ketan
Jelas posting dan pra-kenaikan memiliki semantik yang berbeda dan saya yakin semua orang setuju bahwa ketika hasilnya digunakan, Anda harus menggunakan operator yang sesuai. Saya pikir pertanyaannya adalah apa yang harus dilakukan ketika hasilnya dibuang (seperti dalam
for
loop). Jawaban untuk pertanyaan ini (IMHO) adalah bahwa, karena pertimbangan kinerja dapat diabaikan, Anda harus melakukan apa yang lebih alami. Bagi saya sendiri++i
lebih alami tetapi pengalaman saya memberi tahu saya bahwa saya adalah minoritas dan menggunakani++
akan menyebabkan lebih sedikit overhead logam bagi kebanyakan orang membaca kode Anda.Lagipula itulah alasan mengapa bahasa itu tidak disebut "
++C
".[*] Masukkan diskusi wajib tentang
++C
menjadi nama yang lebih logis.sumber
Ketika tidak menggunakan nilai kembali kompiler dijamin tidak menggunakan sementara dalam kasus ++ i . Tidak dijamin lebih cepat, tapi dijamin tidak lebih lambat.
Saat menggunakan nilai balik i ++ memungkinkan prosesor untuk mendorong kenaikan dan sisi kiri ke dalam pipa karena keduanya tidak saling bergantung. ++ i dapat menghentikan pipa karena prosesor tidak dapat memulai sisi kiri sampai operasi pra-kenaikan telah berkelok-kelok sepanjang jalan. Sekali lagi, warung pipa tidak dijamin, karena prosesor dapat menemukan hal-hal berguna lainnya untuk ditempelkan.
sumber
Mark: Hanya ingin menunjukkan bahwa operator ++ adalah kandidat yang baik untuk diuraikan, dan jika kompiler memilih untuk melakukannya, salinan yang berlebihan akan dihilangkan dalam banyak kasus. (mis. tipe POD, yang biasanya merupakan iterator.)
Yang mengatakan, itu masih gaya yang lebih baik untuk menggunakan ++ iter dalam banyak kasus. :-)
sumber
Perbedaan kinerja antara
++i
dani++
akan lebih jelas ketika Anda menganggap operator sebagai fungsi pengembalian nilai dan bagaimana mereka diimplementasikan. Untuk membuatnya lebih mudah untuk memahami apa yang terjadi, contoh kode berikut akan digunakanint
seolah-olah itu adalah astruct
.++i
menambah variabel, lalu mengembalikan hasilnya. Ini dapat dilakukan di tempat dan dengan waktu CPU minimal, hanya membutuhkan satu baris kode dalam banyak kasus:Tetapi hal yang sama tidak bisa dikatakan
i++
.Pasca-kenaikan,,
i++
sering dianggap mengembalikan nilai asli sebelum bertambah. Namun, fungsi hanya dapat mengembalikan hasil ketika selesai . Akibatnya, menjadi perlu untuk membuat salinan variabel yang berisi nilai asli, menambah variabel, lalu mengembalikan salinan yang menyimpan nilai asli:Ketika tidak ada perbedaan fungsional antara pra-kenaikan dan pasca-kenaikan, kompiler dapat melakukan optimasi sehingga tidak ada perbedaan kinerja antara keduanya. Namun, jika tipe data komposit seperti
struct
atauclass
terlibat, copy constructor akan dipanggil pasca kenaikan, dan itu tidak akan mungkin untuk melakukan optimasi ini jika diperlukan salinan yang dalam. Dengan demikian, pre-increment umumnya lebih cepat dan membutuhkan lebih sedikit memori daripada post-increment.sumber
@ Mark: Saya menghapus jawaban saya sebelumnya karena itu sedikit terbalik, dan pantas menerima downvote untuk itu saja. Saya benar-benar berpikir itu adalah pertanyaan yang bagus dalam arti bahwa itu menanyakan apa yang ada dalam pikiran banyak orang.
Jawaban yang biasa adalah bahwa ++ i lebih cepat dari i ++, dan tidak diragukan lagi, tapi pertanyaan yang lebih besar adalah "kapan Anda peduli?"
Jika fraksi waktu CPU yang dihabiskan untuk meningkatkan iterator kurang dari 10%, maka Anda mungkin tidak peduli.
Jika fraksi waktu CPU yang dihabiskan untuk meningkatkan iterator lebih besar dari 10%, Anda dapat melihat pernyataan mana yang melakukan iterasi. Lihat apakah Anda bisa menambah bilangan bulat daripada menggunakan iterator. Kemungkinannya adalah Anda bisa, dan sementara itu mungkin dalam beberapa hal kurang diinginkan, kemungkinan cukup bagus Anda akan menghemat dasarnya semua waktu yang dihabiskan di iterator tersebut.
Saya telah melihat contoh di mana iterator-incrementing menghabiskan lebih dari 90% waktu. Dalam hal itu, penambahan bilangan bulat akan mengurangi waktu eksekusi pada dasarnya jumlah itu. (Yaitu lebih baik dari 10x speedup)
sumber
@wilhelmtell
Compiler dapat menghilangkan sementara. Verbatim dari utas lainnya:
Kompilator C ++ diizinkan untuk menghilangkan temporari berbasis stack meskipun jika itu mengubah perilaku program. Tautan MSDN untuk VC 8:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx
sumber
Alasan mengapa Anda harus menggunakan ++ i bahkan pada tipe bawaan di mana tidak ada keuntungan kinerja adalah untuk menciptakan kebiasaan yang baik untuk diri sendiri.
sumber
Keduanya sama cepat;) Jika Anda ingin itu adalah perhitungan yang sama untuk prosesor, itu hanya urutan yang dilakukan berbeda.
Misalnya, kode berikut:
Hasilkan perakitan berikut:
Anda melihat bahwa untuk ++ dan b ++ ini termasuk mnemonic, jadi ini operasi yang sama;)
sumber
Pertanyaan yang dimaksud adalah kapan hasilnya tidak digunakan (itu jelas dari pertanyaan untuk C). Adakah yang bisa memperbaikinya karena pertanyaannya adalah "komunitas wiki"?
Tentang optimasi prematur, Knuth sering dikutip. Betul. tetapi Donald Knuth tidak akan pernah membela dengan kode mengerikan yang dapat Anda lihat di hari-hari ini. Pernah melihat a = b + c di antara Java Integers (bukan int)? Itu berjumlah 3 konversi tinju / unboxing. Menghindari hal-hal seperti itu penting. Dan sia-sia menulis i ++ bukan ++ i adalah kesalahan yang sama. EDIT: Seperti yang dikatakan baik oleh phresnel, komentar ini dapat disimpulkan sebagai "optimisasi prematur itu jahat, seperti pesimisasi prematur".
Bahkan fakta bahwa orang lebih terbiasa dengan i ++ adalah warisan C yang tidak menguntungkan, disebabkan oleh kesalahan konseptual oleh K&R (jika Anda mengikuti argumen maksud, itu kesimpulan logis; dan membela K&R karena mereka K&R tidak ada artinya, mereka hebat, tetapi mereka tidak hebat sebagai perancang bahasa; banyak kesalahan dalam desain C ada, mulai dari get () hingga strcpy (), hingga strncpy () API (seharusnya memiliki strlcpy () API sejak hari 1) ).
Btw, saya salah satu dari mereka tidak cukup digunakan untuk C ++ untuk menemukan ++ saya mengganggu untuk dibaca. Namun, saya menggunakannya karena saya mengakui bahwa itu benar.
sumber
++i
lebih menyebalkan daripadai++
(pada kenyataannya, saya merasa lebih keren), tetapi sisa posting Anda mendapat pengakuan penuh dari saya. Mungkin menambahkan poin "optimasi prematur itu jahat, seperti pesimisasi prematur"strncpy
melayani tujuan dalam sistem file yang mereka gunakan pada saat itu; nama file adalah buffer 8 karakter dan tidak harus diakhiri dengan null. Anda tidak dapat menyalahkan mereka karena tidak melihat 40 tahun ke depan evolusi bahasa.strlcpy()
dibenarkan oleh fakta bahwa itu belum ditemukan.Saatnya memberikan permata kebijaksanaan kepada orang-orang;) - ada trik sederhana untuk membuat kenaikan C ++ postfix berperilaku hampir sama dengan kenaikan awalan (Diciptakan ini untuk saya sendiri, tetapi melihatnya juga dalam kode orang lain, jadi saya tidak sendirian).
Pada dasarnya, triknya adalah menggunakan kelas pembantu untuk menunda kenaikan setelah pengembalian, dan RAII datang untuk menyelamatkan
Diciptakan adalah untuk beberapa kode iterators khusus yang berat, dan mengurangi run-time. Biaya awalan vs postfix adalah salah satu referensi sekarang, dan jika ini adalah operator kustom melakukan banyak bergerak, awalan dan postfix menghasilkan run-time yang sama untuk saya.
sumber
++i
lebih cepat daripadai++
karena itu tidak mengembalikan salinan nilai yang lama.Ini juga lebih intuitif:
Contoh C ini mencetak "02" alih-alih "12" yang mungkin Anda harapkan:
Sama untuk C ++ :
sumber