Biz soru var arasında bir performans farkı yoktur i++
ve ++i
C ?
C ++ için cevap nedir?
Biz soru var arasında bir performans farkı yoktur i++
ve ++i
C ?
C ++ için cevap nedir?
Yanıtlar:
[Yönetici Özeti: Kullanmak ++i
için belirli bir nedeniniz yoksa kullanın i++
.]
C ++ için, cevap biraz daha karmaşıktır.
Eğer i
basit bir türü (bir C ++ sınıfının bir örneğidir) 'dir, daha sonra C için verilen cevabı ( "Hayır hiçbir performans farkı yoktur") derleyici kodu üretme olduğundan, tutar.
Ancak, eğer i
bir C ++ sınıfının bir örneğiyse, o zaman i++
ve işlevlerden ++i
birine çağrı operator++
yaparsınız. İşte bu işlevlerin standart bir çifti:
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;
}
Derleyici kod üretmediğinden, yalnızca bir operator++
işlevi çağırdığından , tmp
değişkeni ve ilişkili kopya yapıcısını optimize etmenin bir yolu yoktur . Kopya oluşturucu pahalıysa, bunun önemli bir performans etkisi olabilir.
Evet. Var.
++ operatörü bir işlev olarak tanımlanabilir veya tanımlanmayabilir. İlkel tipler (int, double, ...) için operatörler yerleşiktir, bu nedenle derleyici muhtemelen kodunuzu optimize edebilir. Ancak ++ işlecini tanımlayan bir nesne söz konusu olduğunda işler farklıdır.
Operator ++ (int) işlevi bir kopya oluşturmalıdır. Bunun nedeni, postfix ++ 'ın sahip olduğundan farklı bir değer döndürmesi beklenir: değerini geçici bir değişkende tutmalı, değerini artırmalı ve temp değerini döndürmelidir. ++ (), ++ öneki durumunda, bir kopya oluşturmaya gerek yoktur: nesne kendisini artırabilir ve daha sonra kendisini geri döndürebilir.
İşte konunun bir örneği:
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
}
Operator ++ (int) öğesini her aradığınızda bir kopya oluşturmanız gerekir ve derleyici bu konuda hiçbir şey yapamaz. Seçim yapıldığında, ++ () operatörünü kullanın; bu şekilde bir kopyasını kaydetmezsiniz. Birçok artış (büyük döngü?) Ve / veya büyük nesneler söz konusu olduğunda önemli olabilir.
C t(*this); ++(*this); return t;
İkinci satırda, bu işaretçiyi doğru artırıyorsunuz, bu yüzden bunu t
artırıyorsanız nasıl güncellenir. Bunun değerleri önceden kopyalanmadı t
mı?
The operator++(int) function must create a copy.
hayır öyle değil. Daha fazla kopya yokoperator++()
Artış operatörlerinin farklı çeviri birimlerinde olduğu durum için bir kıstas. G ++ 4.5 ile derleyici.
Şimdilik stil sorunlarını yoksay
// 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;
}
// 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;
}
Sanal makinede g ++ 4.5 ile sonuçlar (zamanlama saniye cinsindendir):
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
Şimdi aşağıdaki dosyayı alalım:
// 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;
}
Artımlılıkta hiçbir şey yapmaz. Bu, artımın sürekli karmaşıklığa sahip olduğu durumu simüle eder.
Sonuçlar artık çok değişken:
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
Önceki değere ihtiyacınız yoksa, ön artışı kullanmayı alışkanlık haline getirin. Yerleşik tiplerle bile tutarlı olun, buna alışacaksınız ve yerleşik bir türü özel bir türle değiştirirseniz gereksiz performans kaybı yaşama riskiyle karşılaşmazsınız.
i++
diyor increment i, I am interested in the previous value, though
.++i
diyor increment i, I am interested in the current value
ya da increment i, no interest in the previous value
. Yine, şimdi olmasanız bile buna alışacaksınız.Erken optimizasyon tüm kötülüklerin köküdür. Erken kötüleşme gibi.
for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }
ve gerçek ağaç yapısını (BSP, kd, Quadtree, Octree Grid, vb.) Böyle bir yineleyici bazı devlet örn korumak gerekir parent node
, child node
, index
böyle falan. Sonuçta, duruşum, sadece birkaç örnek olsa bile, ...
Derleyicinin postfix durumunda geçici değişken kopyayı optimize edemeyeceğini söylemek tamamen doğru değildir. VC ile hızlı bir test, en azından bazı durumlarda bunu yapabileceğini gösterir.
Aşağıdaki örnekte, oluşturulan kod, örneğin önek ve postfix için aynıdır:
#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());
}
++ testFoo veya testFoo ++ ile aynı sonuç kodunu almaya devam edersiniz. Aslında, kullanıcıdan gelen sayıyı okumadan, optimizer her şeyi bir sabite indirdi. Yani bu:
for(int i=0; i<10; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
Sonuç olarak:
00401000 push 0Ah
00401002 push offset string "Value: %d\n" (402104h)
00401007 call dword ptr [__imp__printf (4020A0h)]
Bu nedenle, postfix sürümünün daha yavaş olabileceği kesin olsa da, optimize ediciyi kullanmıyorsanız geçici kopyadan kurtulmak için yeterince iyi olabilir.
Google C ++ Stil Kılavuzu diyor ki:
Öncelik ve Tahmin
Yineleyiciler ve diğer şablon nesnelerle artış ve azalma işleçlerinin önek formunu (++ i) kullanın.
Tanım: Bir değişken arttığında (++ i veya i ++) veya azaltıldığında (--i veya i--) ve ifadenin değeri kullanılmadığında, önceden önizleme (azaltma) veya sonradan azaltma (azaltma) olup olmadığına karar verilmelidir.
Artıları: Dönüş değeri göz ardı edildiğinde, "pre" formu (++ i) hiçbir zaman "post" formundan (i ++) daha az verimli değildir ve genellikle daha verimlidir. Bunun nedeni artım sonrası (veya azalış) ifadesinin değeri olan i'nin bir kopyasının yapılmasını gerektirmesidir. İ bir yineleyici veya diğer skaler olmayan tipte ise, i kopyalamak pahalı olabilir. İki tür artış, değer göz ardı edildiğinde aynı şekilde davrandığından, neden her zaman artış öncesi olmaz?
Eksileri: C geleneği, ifade değeri kullanılmadığında, özellikle döngüler için artış sonrası kullanımı geliştirdi. "Konu" (i) tıpkı İngilizce'de olduğu gibi "fiil" (++) 'ten önce geldiğinden bazıları artım sonrası okumayı daha kolay bulur.
Karar: Basit skaler (nesne olmayan) değerler için bir formu tercih etmek için hiçbir neden yoktur ve biz de izin veririz. Yineleyiciler ve diğer şablon türleri için ön artışı kullanın.
Ben çok yakın zamanda Code Talk Andrew Koenig tarafından mükemmel bir yazı işaret etmek istiyorum.
http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29
Şirketimizde ayrıca, uygun olduğu durumlarda tutarlılık ve performans için ++ iter sözleşmesini kullanıyoruz. Ancak Andrew, performansa karşı niyetle ilgili ayrıntılı bir görünüm ortaya çıkarır. ++ iter yerine iter ++ kullanmak istediğimiz zamanlar vardır.
Bu nedenle, önce niyetinize karar verin ve eğer ön veya sonrası önemli değilse, ekstra nesnenin yaratılmasından kaçınarak ve fırlatarak performans avantajına sahip olacağından pre ile devam edin.
@Ketan
... performansa karşı niyetle ilgili ayrıntılı bilgi ortaya çıkarır. ++ iter yerine iter ++ kullanmak istediğimiz zamanlar vardır.
Açıkçası gönderi ve ön artış farklı semantiklere sahiptir ve eminim herkes sonuç kullanıldığında uygun operatörü kullanmanız gerektiğini kabul eder. Bence sonuç atıldığında ( for
döngülerde olduğu gibi ) ne yapılması gerekir . Cevabı bu soruya (IMHO) performans konuları en iyi ihmal edilebilir olduğundan, daha doğal ne yapmalıyım olmasıdır. Kendim ++i
için daha doğal ama deneyimlerim bana bir azınlıkta olduğumu ve kullanmanın kodunuzu okuyan çoğu insan i++
için daha az metal yüküne neden olacağını söylüyor .
Ne de olsa dilin " ++C
" olarak adlandırılmamasının nedeni budur . [*]
[*] ++C
Daha mantıklı bir isim olmayla ilgili zorunlu tartışmayı ekleyin .
Ne zaman kullanmayan dönüş değeri derleyici durumunda geçici kullanmamayı garanti i ++ . Daha hızlı olduğu garanti edilmez, ancak daha yavaş olmadığı garanti edilir.
Zaman kullanarak dönüş değeri i ++ birbirine bağımlı olmayan yana işlemci boru hattına artım ve sol tarafı, her iki itme sağlar. ++ i boru hattını durdurabilir, çünkü işlemci ön artış işlemi sonuna kadar sol tarafa başlayamaz. Yine, bir boru hattı duraklaması garanti edilmez, çünkü işlemci yapışacak başka yararlı şeyler bulabilir.
Mark: Sadece operatör ++ 'ın inline edilecek iyi adaylar olduğunu belirtmek istedim ve eğer derleyici bunu seçerse, yedek kopya çoğu durumda elenecektir. (örneğin yineleyicilerin genellikle olduğu POD türleri.)
Bununla birlikte, çoğu durumda ++ iter kullanmak hala daha iyi bir stil. :-)
Operatörler arasında değer döndüren fonksiyonlar ve bunların nasıl uygulandığını düşündüğünüzde ++i
ve arasındaki performans farkı i++
daha belirgin olacaktır. Ne olduğunu daha kolay anlamak için aşağıdaki kod örnekleri int
sanki a struct
.
++i
değişkeni artırır, ardından sonucu döndürür. Bu, yerinde ve minimum CPU zamanı ile yapılabilir, çoğu durumda sadece bir kod satırı gerektirir:
int& int::operator++() {
return *this += 1;
}
Ama aynı şey söylenemez i++
.
Artış sonrası, i++
genellikle artmadan önce orijinal değerin döndürüldüğü görülür . Ancak, bir işlev yalnızca bitince bir sonuç döndürebilir . Sonuç olarak, orijinal değeri içeren değişkenin bir kopyasını oluşturmak, değişkeni artırmak ve ardından orijinal değeri tutan kopyayı döndürmek gerekir:
int int::operator++(int& _Val) {
int _Original = _Val;
_Val += 1;
return _Original;
}
Artım öncesi ve artım arasında işlevsel bir fark olmadığında, derleyici, ikisi arasında performans farkı olmayacak şekilde optimizasyon yapabilir. Ancak, struct
veya gibi bir bileşik veri türü class
söz konusu ise, kopya oluşturucu artım sonrası çağrılır ve derin bir kopya gerekiyorsa bu optimizasyonu gerçekleştirmek mümkün olmayacaktır. Bu nedenle, artım öncesi genellikle daha hızlıdır ve artım sonrası olandan daha az bellek gerektirir.
@ Mark: Önceki cevabımı sildim çünkü biraz çevikti ve sadece bunun için bir aşağı oy hak ediyordu. Aslında pek çok insanın kafasında ne olduğunu sorması açısından iyi bir soru olduğunu düşünüyorum.
Her zamanki cevap, ++ i i ++ 'dan daha hızlıdır ve şüphesiz, ama daha büyük soru "ne zaman umursamalısınız?"
Yineleyicileri artırmak için harcanan CPU zamanının oranı% 10'dan azsa, o zaman umursamazsınız.
Yineleyicilerin artırılmasında harcanan CPU zamanının oranı% 10'dan fazlaysa, bu ifadelerin hangi ifadeleri yaptığını görebilirsiniz. Yineleyicileri kullanmak yerine tamsayıları artırıp arttıramayacağınıza bakın. Şansınız olabilir ve bir anlamda daha az arzu edilebilir olsa da, şans oldukça iyidir, bu yineleyicilerde harcanan her zaman esasen tasarruf edersiniz.
Yineleyicinin artmasının zamanın% 90'ından fazlasını tükettiği bir örnek gördüm. Bu durumda, tamsayıyı arttırmak yürütme süresini esasen bu miktar kadar azalttı. (yani 10 kat daha hızlı)
@wilhelmtell
Derleyici geçici olanı kaldırabilir. Diğer konudan Verbatim:
C ++ derleyicisi, program davranışını değiştirse bile, yığın tabanlı geçici sürümleri ortadan kaldırmasına izin verilir. VC 8 için MSDN bağlantısı:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx
Performans avantajı olmayan yerleşik türlerde bile ++ i kullanmanızın bir nedeni, kendiniz için iyi bir alışkanlık oluşturmaktır.
Her ikisi de aynı derecede hızlı;) İşlemci için aynı hesaplama olmasını istiyorsanız, sadece farklı olduğu sırada yapılır.
Örneğin, aşağıdaki kod:
#include <stdio.h>
int main()
{
int a = 0;
a++;
int b = 0;
++b;
return 0;
}
Aşağıdaki montajı yapın:
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
Bir ++ ve b ++ için bir anımsatıcı olduğunu görüyorsunuz, bu yüzden aynı işlem;)
Amaçlanan soru, sonucun ne zaman kullanılmadığıyla ilgilidir (bu, C sorusundan açıktır). Soru "topluluk wiki'si" olduğundan biri bunu düzeltebilir mi?
Erken optimizasyonlar hakkında Knuth sıklıkla alıntılanır. Doğru. ama Donald Knuth bu günlerde görebileceğiniz korkunç kodu asla savunmazdı. Hiç Java Tamsayılar arasında (= değil) a = b + c gördünüz mü? Bu, 3 boks / kutudan çıkarma dönüşümü anlamına gelir. Böyle şeylerden kaçınmak önemlidir. Ve gereksiz yere ++ i yerine i ++ yazmak da aynı hatadır. EDIT: phresnel bir yorum güzel koyar, bu "erken optimizasyon gibi erken optimizasyon kötüdür" olarak özetlenebilir.
İnsanların i ++ 'a daha alışkın olmaları bile, K&R tarafından yapılan kavramsal bir hatadan kaynaklanan talihsiz bir C mirasıdır (eğer niyet argümanını takip ederseniz, bu mantıklı bir sonuçtur; ve K&R'yi savunmak çünkü K&R anlamsızdır, harika, ancak dil tasarımcıları kadar iyi değiller; C tasarımında gets () 'dan strcpy ()' e strncpy () API 'ya kadar (1. günden beri strlcpy () API'sine sahip olmalıydı) sayısız hata var. ).
Btw, ben + + okumak için can sıkıcı bulmak için C ++ için yeterli kullanılmamış olanlardan biriyim. Yine de, doğru olduğunu kabul ettiğim için kullanıyorum.
++i
daha fazla sinir bozucu bulamadım i++
(aslında, daha havalı buldum), ancak gönderinizin geri kalanı tam onayımı alıyor. Belki bir nokta ekleyin "erken optimizasyon gibi erken optimizasyon kötüdür"
strncpy
o sırada kullandıkları dosya sistemlerinde bir amaca hizmet etti; dosya adı 8 karakterlik bir arabellektir ve boş değerli sonlandırılması gerekmez. Dil evriminin geleceğinde 40 yıl görmediği için onları suçlayamazsınız.
strlcpy()
henüz icat edilmemişti gerçeğiyle haklı idi.
Bilgelik taşlar ile millet sağlamak için zaman;) - C ++ postfix artışının önek artışı ile hemen hemen aynı davranmasını sağlamak için basit bir hile var (Bunu kendim için icat ettim, ama diğer insanların kodunda da gördüm, bu yüzden değilim tek başına).
Temel olarak, hile, dönüşten sonra artışı ertelemek için yardımcı sınıfı kullanmaktır ve RAII kurtarmaya gelir
#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;
}
Buluş, bazı ağır özel yineleyiciler kodu içindir ve çalışma süresini kısaltır. Önek vs postfix maliyeti şimdi bir referans ve eğer bu yoğun hareket eden özel operatör ise, önek ve postfix benim için aynı çalışma süresini verdi.
++i
i++
değerinin eski bir kopyasını döndürmediğinden daha hızlıdır .
Ayrıca daha sezgisel:
x = i++; // x contains the old value of i
y = ++i; // y contains the new value of i
Bu C örneği , beklediğiniz "12" yerine "02" yazdırır:
#include <stdio.h>
int main(){
int a = 0;
printf("%d", a++);
printf("%d", ++a);
return 0;
}
#include <iostream>
using namespace std;
int main(){
int a = 0;
cout << a++;
cout << ++a;
return 0;
}