Apakah ada perbedaan kinerja antara i ++ dan ++ i di C ++?

352

Kami memiliki pertanyaan apakah ada perbedaan kinerja antara i++dan ++i di C ?

Apa jawaban untuk C ++?

Mark Harrison
sumber
Saya retagged karena kedua tag adalah cara termudah untuk menemukan pertanyaan seperti ini. Saya juga memeriksa orang lain yang tidak memiliki tanda kohesif dan memberi mereka tanda kohesif.
George Stocker
104
Apakah ada perbedaan kinerja antara menggunakan C ++ dan ++ C?
new123456
2
Artikel: Apakah masuk akal untuk menggunakan prefix increment operator ++ itu dan bukannya postfix operator ++ untuk iterators? - viva64.com/en/b/0093

Jawaban:

426

[Ringkasan Eksekutif: Gunakan ++ijika Anda tidak memiliki alasan khusus untuk digunakan i++.]

Untuk C ++, jawabannya sedikit lebih rumit.

Jika iadalah tipe sederhana (bukan turunan dari kelas C ++), maka jawaban yang diberikan untuk C ("Tidak ada perbedaan kinerja") berlaku, karena kompiler menghasilkan kode.

Namun, jika iadalah turunan dari kelas C ++, maka i++dan ++imembuat panggilan ke salah satu operator++fungsi. Berikut ini pasangan standar dari fungsi-fungsi ini:

Foo& Foo::operator++()   // called for ++i
{
    this->data += 1;
    return *this;
}

Foo Foo::operator++(int ignored_dummy_value)   // called for i++
{
    Foo tmp(*this);   // variable "tmp" cannot be optimized away by the compiler
    ++(*this);
    return tmp;
}

Karena kompiler tidak menghasilkan kode, tetapi hanya memanggil operator++fungsi, tidak ada cara untuk mengoptimalkan tmpvariabel dan penyalin salinan yang terkait. Jika pembuat salinan mahal, maka ini dapat memiliki dampak kinerja yang signifikan.

Mark Harrison
sumber
3
Apa yang dapat dihindari oleh kompiler adalah salinan kedua untuk mengembalikan tmp, dengan mengalokasikan tmp di pemanggil, melalui NRVO, sebagaimana disebutkan oleh komentar lain.
Blaisorblade
7
Tidak bisakah kompiler menghindari ini sama sekali jika operator ++ disisipi?
Eduard - Gabriel Munteanu
16
Ya, jika operator ++ sebaris dan tmp tidak pernah digunakan, ia dapat dihapus kecuali konstruktor atau destruktor objek tmp memiliki efek samping.
Zan Lynx
5
@ Kriss: perbedaan antara C dan C ++ adalah bahwa dalam C Anda memiliki jaminan bahwa operator akan digarisbawahi, dan pada saat itu pengoptimal yang layak akan dapat menghapus perbedaan; alih-alih dalam C ++ Anda tidak dapat mengasumsikan inlining - tidak selalu.
Blaisorblade
3
Saya akan memberi +1 JIKA jawabannya menyebutkan sesuatu tentang kelas yang menyimpan pointer (apakah otomatis, cerdas, atau primitif) ke memori (tumpukan) yang dialokasikan secara dinamis, di mana konstruktor salin harus melakukan salinan yang dalam. Dalam kasus seperti itu, tidak ada argumen, ++ i mungkin urutan besarnya lebih efisien daripada i ++. Kuncinya adalah membiasakan menggunakan pra-kenaikan setiap kali semantik pasca-kenaikan sebenarnya tidak diperlukan oleh algoritme Anda, dan Anda kemudian akan terbiasa menulis kode yang secara alami cocok untuk efisiensi yang lebih besar, terlepas dari bagaimana baik kompiler Anda dapat mengoptimalkan.
phonetagger
64

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:

struct C
{
    C& operator++();      // prefix
    C  operator++(int);   // postfix

private:

    int i_;
};

C& C::operator++()
{
    ++i_;
    return *this;   // self, no copy created
}

C C::operator++(int ignored_dummy_value)
{
    C t(*this);
    ++(*this);
    return t;   // return a copy
}

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.

wilhelmtell
sumber
2
"Operator pra kenaikan memperkenalkan ketergantungan data dalam kode: CPU harus menunggu operasi kenaikan untuk diselesaikan sebelum nilainya dapat digunakan dalam ekspresi. Pada CPU yang disalurkan melalui pipa, ini memperkenalkan sebuah kios. Tidak ada ketergantungan data untuk operator kenaikan pos. " ( Game Engine Architecture (edisi ke-2) ) Jadi, jika salinan kenaikan posting tidak intensif secara komputasi, masih bisa mengalahkan kenaikan pra.
Matthias
Dalam kode postfix, bagaimana cara kerjanya C t(*this); ++(*this); return t;Di baris kedua, Anda menambah pointer ini dengan benar, jadi bagaimana cara tdiperbarui jika Anda menambah ini. Bukankah nilai-nilai ini sudah disalin t?
rasen58
The operator++(int) function must create a copy.Tidak, bukan itu. Tidak lebih dari salinanoperator++()
Severin Pappadeux
47

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

// a.cc
#include <ctime>
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};

int main () {
    Something s;

    for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
    std::clock_t a = clock();
    for (int i=0; i<1024*1024*30; ++i) ++s;
    a = clock() - a;

    for (int i=0; i<1024*1024*30; ++i) s++; // warm up
    std::clock_t b = clock();
    for (int i=0; i<1024*1024*30; ++i) s++;
    b = clock() - b;

    std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
              << ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
    return 0;
}

O (n) kenaikan

Uji

// b.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    for (auto it=data.begin(), end=data.end(); it!=end; ++it)
        ++*it;
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

Hasil

Hasil (timing dalam detik) dengan g ++ 4.5 pada mesin virtual:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      1.70  2.39
-DPACKET_SIZE=50 -O3      0.59  1.00
-DPACKET_SIZE=500 -O1    10.51 13.28
-DPACKET_SIZE=500 -O3     4.28  6.82

O (1) kenaikan

Uji

Sekarang mari kita ambil file berikut:

// c.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

Itu tidak melakukan apa-apa dalam penambahan. Ini mensimulasikan kasus ketika peningkatan memiliki kompleksitas konstan.

Hasil

Hasil sekarang sangat bervariasi:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      0.05   0.74
-DPACKET_SIZE=50 -O3      0.08   0.97
-DPACKET_SIZE=500 -O1     0.05   2.79
-DPACKET_SIZE=500 -O3     0.08   2.18
-DPACKET_SIZE=5000 -O3    0.07  21.90

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++kata increment i, I am interested in the previous value, though.
  • ++ikata increment i, I am interested in the current valueatau increment 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.

phresnel
sumber
1
Tes menarik. Sekarang, hampir dua setengah tahun kemudian, gcc 4,9 dan Dentang 3,4 menunjukkan tren yang sama. Dentang sedikit lebih cepat dengan keduanya, tetapi perbedaan antara pre dan postfix lebih buruk daripada gcc.
kunyah kaus kaki
Apa yang ingin saya lihat adalah contoh dunia nyata di mana ++ i / i ++ membuat perbedaan. Sebagai contoh, apakah itu membuat perbedaan pada salah satu iterators std?
Jakob Schou Jensen
@JakobSchouJensen: Ini cukup dimaksudkan sebagai contoh dunia nyata. Pertimbangkan aplikasi besar, dengan struktur pohon yang kompleks (misalnya pohon kd, pohon quad) atau wadah besar yang digunakan dalam template ekspresi (untuk memaksimalkan throughput data pada perangkat keras SIMD). Jika itu membuat perbedaan di sana, saya tidak begitu yakin mengapa seseorang akan mundur ke pasca-kenaikan untuk kasus-kasus tertentu jika itu tidak diperlukan semantik-bijaksana.
Sebastian Mach
@phrnelnel: Saya tidak berpikir operator ++ dalam template ekspresi sehari-hari Anda - apakah Anda memiliki contoh aktual ini? Penggunaan khas operator ++ adalah pada bilangan bulat dan iterator. Itu adalah saya pikir itu akan menarik untuk mengetahui apakah ada perbedaan (tidak ada perbedaan pada bilangan bulat tentu saja - tetapi iterator).
Jakob Schou Jensen
@JakobSchouJensen: Tidak ada contoh bisnis aktual, tetapi beberapa aplikasi pengolah angka tempat Anda menghitung barang. Iterator Wrt, pertimbangkan pelacak sinar yang ditulis dalam gaya C ++ idiomatik, dan Anda memiliki iterator untuk traversal pertama-kedalaman, sehingga 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, misalnya parent node, child node, indexdan hal-hal seperti itu. Secara keseluruhan, sikap saya adalah, bahkan jika hanya ada beberapa contoh, ...
Sebastian Mach
20

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:

#include <stdio.h>

class Foo
{
public:

    Foo() { myData=0; }
    Foo(const Foo &rhs) { myData=rhs.myData; }

    const Foo& operator++()
    {
        this->myData++;
        return *this;
    }

    const Foo operator++(int)
    {
        Foo tmp(*this);
        this->myData++;
        return tmp;
    }

    int GetData() { return myData; }

private:

    int myData;
};

int main(int argc, char* argv[])
{
    Foo testFoo;

    int count;
    printf("Enter loop count: ");
    scanf("%d", &count);

    for(int i=0; i<count; i++)
    {
        testFoo++;
    }

    printf("Value: %d\n", testFoo.GetData());
}

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:

for(int i=0; i<10; i++)
{
    testFoo++;
}

printf("Value: %d\n", testFoo.GetData());

Menghasilkan sebagai berikut:

00401000  push        0Ah  
00401002  push        offset string "Value: %d\n" (402104h) 
00401007  call        dword ptr [__imp__printf (4020A0h)] 

Jadi, walaupun tentu saja versi postfix bisa lebih lambat, mungkin saja pengoptimal akan cukup baik untuk menyingkirkan salinan sementara jika Anda tidak menggunakannya.

James Sutherland
sumber
8
Anda lupa mencatat poin penting bahwa di sini semuanya disatukan. Jika definisi operator tidak tersedia, salinan yang dilakukan dalam kode out-of-line tidak dapat dihindari; dengan inlining optimasinya cukup jelas, sehingga setiap kompiler akan melakukannya.
Blaisorblade
14

The Google C ++ Style Guide mengatakan:

Preincrement dan Predecrement

Gunakan formulir awalan (++ i) dari operator increment dan decrement dengan iterator dan objek templat lainnya.

Definisi: Ketika suatu variabel bertambah (++ i atau i ++) atau dikurangi (--i atau i--) dan nilai dari ekspresi tidak digunakan, kita harus memutuskan apakah akan preincrement (pengurangan) atau postincrement (pengurangan).

Pro: Ketika nilai kembali diabaikan, bentuk "pra" (++ i) tidak pernah kurang efisien daripada bentuk "pos" (i ++), dan seringkali lebih efisien. Ini karena post-increment (atau decrement) membutuhkan salinan i untuk dibuat, yang merupakan nilai dari ekspresi. Jika saya seorang iterator atau tipe non-skalar lainnya, menyalin saya bisa jadi mahal. Karena dua jenis kenaikan berperilaku sama ketika nilainya diabaikan, mengapa tidak selalu selalu pra-kenaikan?

Cons: Tradisi dikembangkan, dalam C, menggunakan post-increment ketika nilai ekspresi tidak digunakan, terutama untuk loop. Beberapa menemukan post-increment lebih mudah dibaca, karena "subjek" (i) mendahului "kata kerja" (++), sama seperti dalam bahasa Inggris.

Keputusan: Untuk nilai skalar (non-objek) sederhana, tidak ada alasan untuk memilih satu bentuk dan kami mengizinkan keduanya. Untuk iterator dan tipe templat lainnya, gunakan pra-kenaikan.

martjno
sumber
1
"Keputusan: Untuk nilai skalar (bukan-objek) sederhana, tidak ada alasan untuk memilih satu formulir dan kami mengizinkannya. Untuk iterator dan tipe templat lainnya, gunakan pra-kenaikan."
Nosredna
2
Eh, ..., dan apa itu sesuatu?
Sebastian Mach
Tautan yang disebutkan dalam jawaban saat ini rusak
karol
4

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
4

@Ketan

... memunculkan detail yang kelihatannya terkait dengan niat vs kinerja. Ada kalanya kita ingin menggunakan iter ++ daripada ++ iter.

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 forloop). Jawaban untuk pertanyaan ini (IMHO) adalah bahwa, karena pertimbangan kinerja dapat diabaikan, Anda harus melakukan apa yang lebih alami. Bagi saya sendiri ++ilebih alami tetapi pengalaman saya memberi tahu saya bahwa saya adalah minoritas dan menggunakan i++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 ++Cmenjadi nama yang lebih logis.

Motti
sumber
4
@Motti: (bercanda) Nama C ++ logis jika Anda mengingat Bjarne Stroustrup C ++ awalnya mengkodekannya sebagai pra-kompiler yang menghasilkan program C. Karenanya C ++ mengembalikan nilai C lama. Atau mungkin untuk meningkatkan bahwa C ++ agak cacat secara konsep dari awal.
kriss
4
  1. ++ i - lebih cepat tidak menggunakan nilai kembali
  2. i ++ - lebih cepat menggunakan nilai kembali

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.

Hans Malherbe
sumber
3

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. :-)

0124816
sumber
3

Perbedaan kinerja antara ++idan i++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 digunakan intseolah-olah itu adalah a struct.

++imenambah variabel, lalu mengembalikan hasilnya. Ini dapat dilakukan di tempat dan dengan waktu CPU minimal, hanya membutuhkan satu baris kode dalam banyak kasus:

int& int::operator++() { 
     return *this += 1;
}

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:

int int::operator++(int& _Val) {
    int _Original = _Val;
    _Val += 1;
    return _Original;
}

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 structatau classterlibat, 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.

DragonLord
sumber
1

@ 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)

Mike Dunlavey
sumber
1

@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

Mat Noguchi
sumber
1
Itu tidak relevan. NRVO menghindari perlunya menyalin t di "CC :: operator ++ (int)" kembali ke pemanggil, tetapi i ++ masih akan menyalin nilai lama pada tumpukan pemanggil. Tanpa NRVO, i ++ membuat 2 salinan, satu untuk t dan satu lagi ke pemanggil.
Blaisorblade
0

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.

Josh
sumber
3
Maaf, tapi itu menggangguku. Siapa bilang itu "kebiasaan baik", padahal hampir tidak pernah penting? Jika orang ingin menjadikannya bagian dari disiplin mereka, itu bagus, tapi mari kita bedakan alasan signifikan dari masalah selera pribadi.
Mike Dunlavey
@ MikeDunlavey ok, jadi sisi mana yang biasanya Anda gunakan ketika tidak masalah? xD itu salah satu atau yang lain bukan! posting ++ (jika Anda menggunakannya dengan makna umum. perbarui, kembalikan yang lama) benar-benar kalah dengan ++ pre (perbarui, kembalikan) tidak pernah ada alasan Anda ingin memiliki kinerja yang kurang. dalam kasus di mana Anda ingin memperbaruinya setelah itu, programmer bahkan tidak akan melakukan posting ++ sama sekali. tidak perlu membuang waktu untuk menyalin ketika kita sudah memilikinya. perbarui setelah kami menggunakannya. maka kompiler memiliki akal sehat yang Anda inginkan.
Puddle
@Puddle: Ketika saya mendengar ini: "tidak pernah ada alasan Anda ingin memiliki kinerja yang kurang" Aku tahu aku mendengar "sen bijak - pon bodoh". Anda harus memiliki apresiasi terhadap besaran yang terlibat. Hanya jika ini menyumbang lebih dari 1% dari waktu yang terlibat Anda bahkan harus memikirkannya. Biasanya, jika Anda berpikir tentang hal ini, ada juta-kali lebih besar masalah yang Anda tidak mempertimbangkan, dan ini adalah apa yang membuat perangkat lunak jauh lebih lambat daripada bisa.
Mike Dunlavey
@MikeDunlavey memuntahkan omong kosong untuk memuaskan ego Anda. Anda mencoba terdengar seperti bhikkhu yang bijaksana, namun Anda tidak mengatakan apa-apa. besarnya yang terlibat ... jika hanya lebih dari 1% dari waktu Anda harus peduli ... xD dribble mutlak. jika tidak efisien, ada baiknya mengetahui dan memperbaikinya. kami di sini merenungkan ini untuk alasan yang tepat! kami tidak khawatir tentang seberapa banyak yang bisa kami dapatkan dari pengetahuan ini. dan ketika saya mengatakan Anda tidak ingin kinerja kurang, silakan, jelaskan satu skenario sialan itu. MR WISE!
Puddle
0

Keduanya sama cepat;) Jika Anda ingin itu adalah perhitungan yang sama untuk prosesor, itu hanya urutan yang dilakukan berbeda.

Misalnya, kode berikut:

#include <stdio.h>

int main()
{
    int a = 0;
    a++;
    int b = 0;
    ++b;
    return 0;
}

Hasilkan perakitan berikut:

 0x0000000100000f24 <main+0>: push   %rbp
 0x0000000100000f25 <main+1>: mov    %rsp,%rbp
 0x0000000100000f28 <main+4>: movl   $0x0,-0x4(%rbp)
 0x0000000100000f2f <main+11>:    incl   -0x4(%rbp)
 0x0000000100000f32 <main+14>:    movl   $0x0,-0x8(%rbp)
 0x0000000100000f39 <main+21>:    incl   -0x8(%rbp)
 0x0000000100000f3c <main+24>:    mov    $0x0,%eax
 0x0000000100000f41 <main+29>:    leaveq 
 0x0000000100000f42 <main+30>:    retq

Anda melihat bahwa untuk ++ dan b ++ ini termasuk mnemonic, jadi ini operasi yang sama;)

Geoffroy
sumber
Itu C, sementara OP bertanya C ++. Dalam C itu sama. Dalam C ++ yang lebih cepat adalah ++ i; karena objeknya. Namun beberapa kompiler dapat mengoptimalkan operator pasca kenaikan.
Wiggler Jtag
0

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.

Blaisorblade
sumber
Saya melihat Anda sedang mengerjakan Ph.D. dengan minat pada optimasi kompiler dan hal-hal semacam itu. Itu hebat, tapi jangan lupa akademisi adalah ruang gema, dan akal sehat sering ditinggalkan di luar pintu, setidaknya di CS Anda mungkin tertarik dengan ini: stackoverflow.com/questions/1303899/…
Mike Dunlavey
Saya tidak pernah menemukan yang ++ilebih menyebalkan daripada i++(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"
Sebastian Mach
strncpymelayani 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.
MM
@MattMcNabb: bukankah 8 karakter nama file MS-DOS eksklusif? C ditemukan dengan Unix. Bagaimanapun, bahkan jika strncpy ada benarnya, kekurangan strlcpy tidak sepenuhnya dibenarkan: bahkan C asli memiliki array yang Anda tidak boleh meluap, yang membutuhkan strlcpy; paling banyak, mereka hanya penyerang yang hilang yang bermaksud mengeksploitasi bug. Tetapi orang tidak dapat mengatakan bahwa memperkirakan masalah ini sepele, jadi jika saya menulis ulang posting saya, saya tidak akan menggunakan nada yang sama.
Blaisorblade
@ Blaisorblade: Seingat saya, nama file UNIX awal dibatasi hingga 14 karakter. Kurangnya strlcpy()dibenarkan oleh fakta bahwa itu belum ditemukan.
Keith Thompson
-1

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

#include <iostream>

class Data {
    private: class DataIncrementer {
        private: Data& _dref;

        public: DataIncrementer(Data& d) : _dref(d) {}

        public: ~DataIncrementer() {
            ++_dref;
        }
    };

    private: int _data;

    public: Data() : _data{0} {}

    public: Data(int d) : _data{d} {}

    public: Data(const Data& d) : _data{ d._data } {}

    public: Data& operator=(const Data& d) {
        _data = d._data;
        return *this;
    }

    public: ~Data() {}

    public: Data& operator++() { // prefix
        ++_data;
        return *this;
    }

    public: Data operator++(int) { // postfix
        DataIncrementer t(*this);
        return *this;
    }

    public: operator int() {
        return _data;
    }
};

int
main() {
    Data d(1);

    std::cout <<   d << '\n';
    std::cout << ++d << '\n';
    std::cout <<   d++ << '\n';
    std::cout << d << '\n';

    return 0;
}

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.

Severin Pappadeux
sumber
-5

++ilebih cepat daripada i++karena itu tidak mengembalikan salinan nilai yang lama.

Ini juga lebih intuitif:

x = i++;  // x contains the old value of i
y = ++i;  // y contains the new value of i 

Contoh C ini mencetak "02" alih-alih "12" yang mungkin Anda harapkan:

#include <stdio.h>

int main(){
    int a = 0;
    printf("%d", a++);
    printf("%d", ++a);
    return 0;
}

Sama untuk C ++ :

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
Cees Timmerman
sumber