Apakah ada perbedaan kinerja antara i++
dan ++i
jika nilai yang dihasilkan tidak digunakan?
Apakah ada perbedaan kinerja antara i++
dan ++i
jika nilai yang dihasilkan tidak digunakan?
Jawaban:
Ringkasan eksekutif: Tidak.
i++
berpotensi lebih lambat daripada ++i
, karena nilai lama i
mungkin perlu disimpan untuk digunakan nanti, tetapi dalam praktiknya semua kompiler modern akan mengoptimalkan ini.
Kita dapat mendemonstrasikan ini dengan melihat kode untuk fungsi ini, baik dengan ++i
dan i++
.
$ cat i++.c
extern void g(int i);
void f()
{
int i;
for (i = 0; i < 100; i++)
g(i);
}
File-file itu sama, kecuali untuk ++i
dan i++
:
$ diff i++.c ++i.c
6c6
< for (i = 0; i < 100; i++)
---
> for (i = 0; i < 100; ++i)
Kami akan mengkompilasinya, dan juga mendapatkan assembler yang dihasilkan:
$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c
Dan kita dapat melihat bahwa kedua objek yang dihasilkan dan file assembler adalah sama.
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e
$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
++i
bukan i++
. Sama sekali tidak ada alasan untuk tidak melakukannya, dan jika perangkat lunak Anda pernah melewati rantai alat yang tidak mengoptimalkannya, perangkat lunak Anda akan lebih efisien. Mengingat itu mudah untuk mengetik ++i
seperti halnya mengetik i++
, sebenarnya tidak ada alasan untuk tidak menggunakan ++i
di tempat pertama.
Dari Efisiensi versus niat oleh Andrew Koenig:
Pertama, jauh dari jelas bahwa
++i
lebih efisien daripadai++
, setidaknya di mana variabel integer diperhatikan.
Dan:
Jadi pertanyaan yang harus ditanyakan bukanlah yang mana dari dua operasi ini yang lebih cepat, yang mana dari dua operasi ini yang mengungkapkan lebih akurat apa yang ingin Anda capai. Saya menyampaikan bahwa jika Anda tidak menggunakan nilai ekspresi, tidak pernah ada alasan untuk menggunakan
i++
bukan++i
, karena tidak pernah ada alasan untuk menyalin nilai dari variabel, kenaikan variabel, dan kemudian melemparkan salinan pergi.
Jadi, jika nilai yang dihasilkan tidak digunakan, saya akan gunakan ++i
. Tetapi bukan karena lebih efisien: karena dengan tepat menyatakan niat saya.
i++
dengan cara yang sama saya akan kode i += n
atau i = i + n
, yaitu, dalam bentuk objek kata kerja target , dengan operan target di sebelah kiri operator kata kerja . Dalam kasus , tidak ada objek yang benar , tetapi aturan tetap berlaku, menjaga target di sebelah kiri operator kata kerja . i++
Jawaban yang lebih baik adalah bahwa ++i
terkadang akan lebih cepat tetapi tidak pernah lebih lambat.
Semua orang tampaknya berasumsi bahwa itu i
adalah tipe bawaan seperti int
. Dalam hal ini tidak akan ada perbedaan yang terukur.
Namun jika i
tipe yang kompleks maka Anda mungkin menemukan perbedaan yang terukur. Untuki++
Anda harus membuat salinan kelas Anda sebelum menambahkannya. Tergantung pada apa yang terlibat dalam salinan itu memang bisa lebih lambat karena dengan ++it
Anda hanya dapat mengembalikan nilai akhir.
Foo Foo::operator++()
{
Foo oldFoo = *this; // copy existing value - could be slow
// yadda yadda, do increment
return oldFoo;
}
Perbedaan lainnya adalah bahwa ++i
Anda memiliki opsi untuk mengembalikan referensi alih-alih nilai. Sekali lagi, tergantung pada apa yang terlibat dalam membuat salinan objek Anda ini bisa lebih lambat.
Contoh dunia nyata di mana ini bisa terjadi adalah penggunaan iterator. Menyalin iterator tidak mungkin menjadi masalah besar dalam aplikasi Anda, tetapi masih merupakan praktik yang baik untuk membiasakan diri menggunakan ++i
alih-alih i++
ketika hasilnya tidak terpengaruh.
Jawaban singkat:
Tidak pernah ada perbedaan antara i++
dan ++i
dalam hal kecepatan. Kompiler yang baik seharusnya tidak menghasilkan kode yang berbeda dalam dua kasus.
Jawaban panjang:
Apa yang gagal dijawab oleh setiap jawaban lainnya adalah perbedaannya ++i
versus i++
hanya masuk akal dalam ekspresi yang ditemukan.
Dalam kasus for(i=0; i<n; i++)
, i++
sendirian dalam ekspresi sendiri: ada titik urutan sebelum i++
dan ada satu setelahnya. Dengan demikian satu-satunya kode mesin yang dihasilkan adalah "meningkat i
dengan 1
" dan itu didefinisikan dengan baik bagaimana ini diurutkan sehubungan dengan sisa program. Jadi jika Anda mengubahnya menjadi awalan ++
, itu tidak masalah sedikit pun, Anda masih akan mendapatkan kode mesin "meningkat i
sebesar1
".
Perbedaan antara ++i
dan i++
hanya masalah dalam ekspresi seperti array[i++] = x;
versusarray[++i] = x;
. Beberapa orang mungkin berdebat dan mengatakan bahwa postfix akan lebih lambat dalam operasi seperti itu karena register tempat i
tinggal harus dimuat ulang nanti. Tetapi kemudian perhatikan bahwa kompiler bebas untuk memesan instruksi Anda dengan cara apa pun yang diinginkan, asalkan itu tidak "mematahkan perilaku mesin abstrak" sebagaimana standar C menyebutnya.
Jadi, sementara Anda mungkin menganggap bahwa array[i++] = x;
akan diterjemahkan ke kode mesin sebagai:
i
dalam register A.i
dalam register A // tidak efisien karena instruksi tambahan di sini, kami sudah melakukan ini sekali.i
.kompiler mungkin juga menghasilkan kode lebih efisien, seperti:
i
dalam register A.i
.Hanya karena Anda sebagai programmer C dilatih untuk berpikir bahwa postfix itu ++
terjadi pada akhirnya, kode mesin tidak harus dipesan dengan cara itu.
Jadi tidak ada perbedaan antara prefix dan postfix ++
dalam C. Sekarang apa yang Anda sebagai programmer C harus bervariasi, adalah orang-orang yang tidak konsisten menggunakan awalan dalam beberapa kasus dan postfix dalam kasus lain, tanpa alasan mengapa. Ini menunjukkan bahwa mereka tidak yakin tentang bagaimana C bekerja atau bahwa mereka memiliki pengetahuan bahasa yang salah. Ini selalu pertanda buruk, yang pada gilirannya menunjukkan bahwa mereka membuat keputusan yang dipertanyakan lainnya dalam program mereka, berdasarkan takhayul atau "dogma agama".
"Awalan ++
selalu lebih cepat" memang merupakan salah satu dogma palsu yang umum di antara calon programmer C.
Mengambil daun dari Scott Meyers, Lebih Efektif c ++ Item 6: Bedakan antara bentuk peningkatan dan penurunan awalan dan postfix .
Versi awalan selalu lebih disukai daripada postfix sehubungan dengan objek, terutama dalam hal iterator.
Alasan untuk ini jika Anda melihat pola panggilan operator.
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
Melihat contoh ini, mudah untuk melihat bagaimana operator awalan akan selalu lebih efisien daripada postfix. Karena kebutuhan akan objek sementara dalam penggunaan postfix.
Inilah sebabnya mengapa ketika Anda melihat contoh menggunakan iterator mereka selalu menggunakan versi awalan.
Tetapi ketika Anda menunjukkan untuk int tidak ada perbedaan secara efektif karena optimasi kompiler yang dapat terjadi.
Berikut ini pengamatan tambahan jika Anda khawatir tentang optimasi mikro. Mengurangi loop bisa 'mungkin' lebih efisien daripada menambah loop (tergantung pada arsitektur set instruksi misalnya ARM), mengingat:
for (i = 0; i < 100; i++)
Pada setiap loop Anda, Anda akan memiliki satu instruksi masing-masing untuk:
1
ke i
. i
kurang dari a 100
.i
kurang dari a 100
.Sedangkan loop decrementing:
for (i = 100; i != 0; i--)
Loop akan memiliki instruksi untuk masing-masing:
i
, mengatur flag status register CPU.Z==0
).Tentu saja ini hanya berfungsi ketika mengurangi ke nol!
Diingat dari Panduan Pengembang Sistem ARM.
Tolong jangan biarkan pertanyaan "mana yang lebih cepat" menjadi faktor penentu yang digunakan. Kemungkinannya adalah Anda tidak akan terlalu peduli, dan selain itu, waktu membaca programmer jauh lebih mahal daripada waktu mesin.
Gunakan yang paling masuk akal bagi manusia yang membaca kode.
Pertama-tama: Perbedaan antara i++
dan ++i
dapat diabaikan dalam C.
Untuk detailnya.
++i
lebih cepatDalam C ++, ++i
lebih efisien jika iff i
adalah semacam objek dengan operator kenaikan berlebih.
Mengapa?
Dalam ++i
, objek pertama kali bertambah, dan selanjutnya dapat dilewatkan sebagai referensi const ke fungsi lainnya. Ini tidak mungkin jika ungkapannya adalah foo(i++)
karena sekarang kenaikan harus dilakukan sebelum foo()
dipanggil, tetapi nilai lama perlu diteruskan ke foo()
. Akibatnya, kompiler terpaksa membuat salinan i
sebelum mengeksekusi operator kenaikan pada aslinya. Panggilan konstruktor / destruktor tambahan adalah bagian yang buruk.
Seperti disebutkan di atas, ini tidak berlaku untuk tipe fundamental.
i++
mungkin lebih cepatJika tidak ada konstruktor / destruktor yang perlu dipanggil, yang selalu terjadi di C, ++i
dan i++
harus sama cepatnya, bukan? Tidak. Mereka sebenarnya sama cepatnya, tetapi mungkin ada perbedaan kecil, yang dijawab oleh sebagian besar penjawab lain.
Bagaimana bisa i++
lebih cepat?
Intinya adalah ketergantungan data. Jika nilai perlu dimuat dari memori, dua operasi selanjutnya harus dilakukan dengan itu, menambahnya, dan menggunakannya. Dengan ++i
, incrementation perlu dilakukan sebelum nilainya dapat digunakan. Dengan i++
, penggunaan tidak tergantung pada kenaikan, dan CPU dapat melakukan operasi penggunaan secara paralel dengan operasi kenaikan. Perbedaannya paling banyak adalah satu siklus CPU, sehingga benar-benar dapat diabaikan, tetapi itu ada. Dan sebaliknya, banyak orang akan berharap.
++i
atau i++
digunakan dalam ekspresi lain, mengubah di antara mereka mengubah semantik ekspresi, sehingga kemungkinan untung / rugi kinerja yang mungkin adalah pertanyaan. Jika mereka berdiri sendiri, yaitu, hasil operasi tidak digunakan segera, maka setiap kompiler yang layak akan mengkompilasinya ke hal yang sama, misalnya INC
instruksi perakitan.
i++
dan ++i
dapat digunakan secara bergantian di hampir setiap situasi yang mungkin dengan menyesuaikan konstanta loop oleh satu, sehingga mereka hampir setara dalam apa yang mereka lakukan untuk programmer. 2) Meskipun keduanya mengkompilasi dengan instruksi yang sama, eksekusi mereka berbeda untuk CPU. Dalam kasus ini i++
, CPU dapat menghitung kenaikan secara paralel dengan beberapa instruksi lain yang menggunakan nilai yang sama (CPU benar-benar melakukan ini!), Sedangkan dengan ++i
CPU harus menjadwalkan instruksi lainnya setelah kenaikan.
if(++foo == 7) bar();
dan if(foo++ == 6) bar();
secara fungsional setara. Namun, yang kedua mungkin satu siklus lebih cepat, karena perbandingan dan kenaikannya dapat dihitung secara paralel oleh CPU. Bukan berarti siklus tunggal ini sangat berarti, tetapi perbedaannya ada di sana.
<
misalnya vs <=
) di mana ++
biasanya digunakan, sehingga konversi antara meskipun sering mudah dilakukan.
@ Mark Meskipun kompiler diperbolehkan untuk mengoptimalkan salinan sementara variabel (berbasis stack) dan gcc (dalam versi terbaru) melakukannya, tidak berarti semua kompiler akan selalu melakukannya.
Saya baru saja mengujinya dengan kompiler yang kami gunakan dalam proyek kami saat ini dan 3 dari 4 tidak mengoptimalkannya.
Jangan pernah menganggap kompiler melakukannya dengan benar, terutama jika kode lebih cepat, tetapi tidak pernah lebih lambat, mudah dibaca.
Jika Anda tidak memiliki implementasi yang sangat bodoh dari salah satu operator dalam kode Anda:
Lebih suka ++ i daripada i ++.
Dalam C, kompiler umumnya dapat mengoptimalkannya sama jika hasilnya tidak digunakan.
Namun, di C ++ jika menggunakan tipe lain yang menyediakan operator ++ mereka sendiri, versi awalan cenderung lebih cepat daripada versi postfix. Jadi, jika Anda tidak memerlukan semantik postfix, lebih baik menggunakan operator awalan.
Saya bisa memikirkan situasi di mana postfix lebih lambat daripada peningkatan awalan:
Bayangkan sebuah prosesor dengan register A
digunakan sebagai akumulator dan itu satu-satunya register yang digunakan dalam banyak instruksi (beberapa mikrokontroler kecil sebenarnya seperti ini).
Sekarang bayangkan program berikut dan terjemahannya ke dalam kumpulan hipotesis:
Peningkatan awalan:
a = ++b + c;
; increment b
LD A, [&b]
INC A
ST A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
Peningkatan postfix:
a = b++ + c;
; load b
LD A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
; increment b
LD A, [&b]
INC A
ST A, [&b]
Perhatikan bagaimana nilai b
paksa dimuat ulang. Dengan kenaikan awalan, kompiler hanya bisa menambah nilai dan terus menggunakannya, mungkin menghindari memuat ulang karena nilai yang diinginkan sudah ada di register setelah kenaikan. Namun, dengan penambahan postfix, kompiler harus berurusan dengan dua nilai, satu yang lama dan satu nilai yang bertambah yang seperti yang saya perlihatkan di atas menghasilkan satu lagi akses memori.
Tentu saja, jika nilai kenaikan tidak digunakan, seperti i++;
pernyataan tunggal , kompiler dapat (dan memang) hanya menghasilkan instruksi kenaikan terlepas dari penggunaan postfix atau awalan.
Sebagai catatan tambahan, saya ingin menyebutkan bahwa ekspresi di mana ada b++
tidak dapat dengan mudah dikonversi menjadi ++b
tanpa usaha tambahan (misalnya dengan menambahkan a - 1
). Jadi membandingkan keduanya jika mereka adalah bagian dari ekspresi tidak benar-benar valid. Seringkali, di mana Anda menggunakan b++
di dalam ekspresi yang tidak dapat Anda gunakan ++b
, jadi bahkan jika ++b
berpotensi lebih efisien, itu hanya akan salah. Pengecualian tentu saja jika ungkapan itu memohon untuk itu (misalnyaa = b++ + 1;
yang dapat diubah menjadia = ++b;
).
Saya telah membaca melalui sebagian besar jawaban di sini dan banyak komentar, dan saya tidak melihat referensi ke salah satu contoh yang saya bisa memikirkan mana i++
yang lebih efisien daripada ++i
(dan mungkin mengejutkan --i
adalah lebih efisien daripada i--
). Itu untuk kompiler C untuk DEC PDP-11!
PDP-11 memiliki instruksi perakitan untuk pra-pengurangan register dan pasca kenaikan, tetapi tidak sebaliknya. Instruksi memungkinkan register "tujuan umum" digunakan sebagai penunjuk tumpukan. Jadi jika Anda menggunakan sesuatu seperti *(i++)
itu bisa dikompilasi menjadi instruksi perakitan tunggal, sementara *(++i)
tidak bisa.
Ini jelas merupakan contoh yang sangat esoteris, tetapi itu memberikan pengecualian di mana peningkatan pasca-lebih efisien (atau saya harus katakan adalah , karena tidak ada banyak permintaan untuk kode PDP-11 C hari ini).
--i
dan i++
.
Saya selalu lebih suka pra-kenaikan, namun ...
Saya ingin menunjukkan bahwa bahkan dalam kasus memanggil fungsi operator ++, kompiler akan dapat mengoptimalkan sementara sementara jika fungsi mendapat inline. Karena operator ++ biasanya pendek dan sering diimplementasikan dalam header, itu kemungkinan akan diikutsertakan.
Jadi, untuk tujuan praktis, kemungkinan tidak ada banyak perbedaan antara kinerja kedua bentuk. Namun, saya selalu lebih suka pra-kenaikan karena tampaknya lebih baik untuk secara langsung mengungkapkan apa yang saya coba katakan, daripada mengandalkan pengoptimal untuk mencari tahu.
Juga, memberikan optmizer lebih sedikit untuk melakukan kemungkinan berarti kompiler berjalan lebih cepat.
C saya agak berkarat, jadi saya minta maaf sebelumnya. Secara cepat, saya bisa mengerti hasilnya. Tapi, saya bingung bagaimana kedua file keluar ke hash MD5 yang sama. Mungkin for for menjalankan hal yang sama, tetapi bukankah 2 baris kode berikut menghasilkan rakitan yang berbeda?
myArray[i++] = "hello";
vs.
myArray[++i] = "hello";
Yang pertama menulis nilai ke array, lalu menambahkan i. Peningkatan kedua saya kemudian menulis ke array. Saya bukan pakar perakitan, tapi saya tidak melihat bagaimana executable yang sama akan dihasilkan oleh 2 baris kode yang berbeda ini.
Hanya dua sen saya.
foo[i++]
ke foo[++i]
tanpa mengubah apa pun akan jelas mengubah semantik program, tetapi pada beberapa prosesor saat menggunakan compiler tanpa optimasi logika loop-mengangkat, incrementing p
dan q
sekali dan kemudian menjalankan loop yang melakukan misalnya *(p++)=*(q++);
akan lebih cepat daripada menggunakan loop yang melakukan *(++pp)=*(++q);
. Untuk pengulangan yang sangat ketat pada beberapa prosesor, perbedaan kecepatan mungkin signifikan (lebih dari 10%), tapi itu mungkin satu-satunya kasus di C di mana kenaikan pasca secara material lebih cepat daripada kenaikan pra.