Arasında bir performans farkı var mı i++
ve ++i
kullanılmaz çıkan değerin olur?
Arasında bir performans farkı var mı i++
ve ++i
kullanılmaz çıkan değerin olur?
Yanıtlar:
Yönetici özeti: Hayır.
i++
++i
eski değerinin i
daha sonra kullanılmak üzere kaydedilmesi gerekebileceğinden potansiyel olarak daha yavaş olabilir, ancak pratikte tüm modern derleyiciler bunu optimize edecektir.
Bunu hem ++i
ve hem de bu işlevin koduna bakarak gösterebiliriz i++
.
$ cat i++.c
extern void g(int i);
void f()
{
int i;
for (i = 0; i < 100; i++)
g(i);
}
Dosyalar ++i
ve dışında aynıdır i++
:
$ diff i++.c ++i.c
6c6
< for (i = 0; i < 100; i++)
---
> for (i = 0; i < 100; ++i)
Onları derleyeceğiz ve oluşturulan derleyiciyi de alacağız:
$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c
Hem üretilen nesne hem de birleştirici dosyalarının aynı olduğunu görebiliriz.
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e
$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
++i
bunun yerine hala iyi bir uygulama olduğunu unutmayın i++
. Kesin olmamak için hiçbir neden yoktur ve yazılımınız onu optimize etmeyen bir araç zincirinden geçerse, yazılımınız daha verimli olacaktır. Bunu göz önünde bulundurarak sadece kadar kolay yazmaktır ++i
yazmanın ne kadar i++
, kullanmıyor olması için hiçbir bahane gerçekten yoktur ++i
ilk etapta.
Gönderen niyet karşısında Verimlilik Andrew Koenig tarafından:
Birincisi, en azından tamsayı değişkenleri söz konusu olduğunda
++i
daha etkili olduğu açıktıri++
.
Ve :
Yani sorulması gereken soru, bu iki işlemden hangisinin daha hızlı olduğu değil, bu iki işlemden hangisini gerçekleştirmeye çalıştığınızı daha doğru bir şekilde ifade etmesidir. İfadenin değerini kullanmıyorsanız,
i++
bunun yerine kullanmak++i
için asla bir neden olmadığını, çünkü bir değişkenin değerini kopyalamak, değişkeni artırmak ve daha sonra kopyayı atmak için hiçbir neden olmadığına inanıyorum .
Yani, sonuçta ortaya çıkan değer kullanılmazsa, kullanırım ++i
. Ama daha verimli olduğu için değil: çünkü niyetimi doğru bir şekilde ifade ediyor.
i++
aynı şekilde ben kod olur i += n
veya i = i + n
biçim içinde, yani hedefin fiil nesne ile, hedefin işlenen sol için fiil operatörü. Durumda, i++
doğru nesne yoktur , ancak hedef fiil operatörünün solunda tutularak kural geçerlidir .
Daha iyi bir cevap, ++i
bazen daha hızlı olacak ama asla daha yavaş olmayacak.
Herkes i
bunun normal yerleşik bir tür olduğunu varsayıyor gibi görünüyor int
. Bu durumda ölçülebilir bir fark olmayacaktır.
Ancak i
karmaşık tip ise , ölçülebilir bir fark bulabilirsiniz. Çünkü i++
artırmadan önce sınıfınızın bir kopyasını oluşturmanız gerekir. Bir kopyaya neyin dahil olduğuna bağlı olarak, gerçekten daha yavaş olabilir, çünkü ++it
sadece son değeri iade edebilirsiniz.
Foo Foo::operator++()
{
Foo oldFoo = *this; // copy existing value - could be slow
// yadda yadda, do increment
return oldFoo;
}
Başka bir fark, ++i
sizinle birlikte bir değer yerine bir referans döndürme seçeneğiniz olmasıdır. Yine, nesnenizin bir kopyasını oluşturmaya neyin bağlı olduğuna bağlı olarak, bu daha yavaş olabilir.
Bunun gerçekleşebileceği gerçek dünya örneği yineleyicilerin kullanımı olacaktır. Bir yineleyiciyi kopyalamanın uygulamanızda bir şişe boynu olması muhtemel değildir, ancak sonucun etkilenmediği yer ++i
yerine kullanma alışkanlığına girmek hala iyi bir uygulamadır i++
.
Kısa cevap:
Hız arasında i++
ve ++i
hızda hiçbir fark yoktur . İyi bir derleyici iki durumda farklı kod oluşturmamalıdır.
Uzun cevap:
Diğer cevapların bahsetmediği şey, ++i
karşısındaki farkın i++
sadece bulduğu ifade içinde mantıklı olduğudur.
Durumunda for(i=0; i<n; i++)
, i++
kendi ifadesinde yalnızdır: önce dizi nokta var i++
ve ondan sonra bir tane var. Bu şekilde oluşturulan tek makine kodu "artış i
ile 1
" ve programın geri kalanına ilişkin olarak sekanslanır ne kadar iyi tanımlanmıştır. Yani önek değiştirmek olsaydı ++
, meselenin ufak, hala sadece makine kodu "artışı alacağı olmazdı i
tarafından 1
".
Arasındaki farklar ++i
ve i++
gibi ifadelerde sadece konularda array[i++] = x;
karşı array[++i] = x;
. Bazıları, bu tür işlemlerde postfix'in daha yavaş olacağını söyleyebilir ve daha i
sonra yeniden yüklenmesi gereken kayıtlar olduğunu söyleyebilir . Ancak, derleyici, C standardının dediği gibi "soyut makinenin davranışını bozmadığı" sürece talimatlarınızı istediği şekilde sipariş etmekte özgür olduğunu unutmayın.
Yani bunun array[i++] = x;
makine koduna çevrildiğini varsayabilirsiniz :
i
A kaydındaki mağaza değeri.i
A kaydındaki mağaza değeri // verimsiz çünkü burada ekstra talimat var, bunu zaten bir kez yaptık.i
.derleyici kodu daha verimli üretebilir, örneğin:
i
A kaydındaki mağaza değeri.i
.Bir C programcısı olarak postfix'in ++
sonunda olduğunu düşünecek şekilde eğitilmiş olmanızdan dolayı , makine kodunun bu şekilde sipariş edilmesi gerekmez.
Bu nedenle ++
C'deki önek ve postfix arasında bir fark yoktur . Şimdi C programcısı olarak çeşitlilik göstermeniz gereken şey, bazı durumlarda tutarsız bir şekilde önek ve diğer durumlarda da postfix kullanan insanlardır. Bu, C'nin nasıl çalıştığından veya dil hakkında yanlış bilgiye sahip olduklarından emin olmadıklarını gösterir. Bu her zaman kötü bir işarettir, programlarında batıl inançlara veya "dini dogmalara" dayanarak başka şüpheli kararlar aldıklarını ileri sürmektedir.
"Önek ++
her zaman daha hızlıdır" aslında C programcıları arasında yaygın olan yanlış bir dogmadır.
Scott Meyers'den bir yaprak almak, Daha Etkili c ++ Madde 6: Artış ve azaltma işlemlerinin önek ve son düzeltme formları arasında ayrım yapın .
Önek sürümü, nesneler açısından, özellikle yineleyiciler açısından postfix'e göre her zaman tercih edilir.
Bunun nedeni, operatörlerin çağrı düzenine bakarsanız.
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
Bu örneğe bakıldığında, önek operatörünün postfix'ten her zaman nasıl daha verimli olacağını görmek kolaydır. Postfix kullanımında geçici bir nesneye ihtiyaç duyulduğundan.
Bu nedenle yineleyicileri kullanan örnekler gördüğünüzde her zaman önek sürümünü kullanırlar.
Ancak int için işaret ettiğiniz gibi, derleyici optimizasyonu nedeniyle etkili bir fark yoktur.
Mikro optimizasyon konusunda endişeleriniz varsa ek bir gözlem. Azalan döngüler, artan döngülerden (muhtemelen komut seti mimarisine, örneğin ARM'ye bağlı olarak) daha etkili olabilir:
for (i = 0; i < 100; i++)
Her döngüde her biri için bir talimatınız olacaktır:
1
için i
. i
bir daha azdır 100
.i
, a'dan küçükse 100
.Oysa azalan bir döngü:
for (i = 100; i != 0; i--)
Döngünün her biri için bir talimatı olacaktır:
i
CPU kayıt durumu bayrağını ayarlayarak azalma .Z==0
) bağlı bir koşullu dal .Tabii ki bu sadece sıfıra inerken işe yarar!
ARM Sistem Geliştirici Kılavuzu'ndan hatırlanmıştır.
Lütfen "hangisinin daha hızlı" sorusunun hangisinin kullanılacağına karar verme faktörü olmasına izin vermeyin. Muhtemelen hiç bu kadar çok umursamayacaksınız ve ayrıca programcı okuma süresi makine zamanından çok daha pahalı.
Kodu okuyan insan için en anlamlı olanı kullanın.
Her şeyden önce: i++
ve arasındaki fark ++i
C cinsinden ayrılmazdır.
Detaylara.
++i
daha hızlıC ++, ++i
daha verimli iff i
aşırı yüklenmiş bir artış operatörü ile bir nesne türüdür.
Neden?
İçinde ++i
, nesne ilk olarak artırılır ve daha sonra başka herhangi bir fonksiyona bir sabit referans olarak geçirilebilir. İfadenin foo(i++)
daha önce yapılması gereken çağrının foo()
çağrılması, ancak eski değerin iletilmesi gerektiği için bu ifade mümkün değilse bu mümkün değildir foo()
. Sonuç olarak, derleyici, i
artan işleci orijinal üzerinde çalıştırmadan önce bir kopyasını almak zorunda kalır . Ek kurucu / yıkıcı çağrıları kötü kısımdır.
Yukarıda belirtildiği gibi, bu temel tipler için geçerli değildir.
i++
olabilirHiçbir kurucu / yıkıcı çağrılmaya ihtiyaç duyulmuyorsa, bu her zaman C'deki durumdur ++i
ve i++
eşit derecede hızlı olmalıdır, değil mi? Hayır. Neredeyse aynı derecede hızlıdırlar, ancak diğer cevaplayıcıların çoğunun yanlış yol aldıkları küçük farklılıklar olabilir.
Nasıl i++
daha hızlı olabilir ?
Mesele veri bağımlılıklarıdır. Değerin bellekten yüklenmesi gerekiyorsa, onunla sonraki iki işlemin yapılması, artırılması ve kullanılması gerekir. İle ++i
, değerin kullanılabilmesi için önce artım yapılmalıdır . İle i++
kullanım, artışa bağlı değildir ve CPU, kullanım işlemini artış işlemine paralel olarak gerçekleştirebilir. Fark en fazla bir CPU döngüsüdür, bu yüzden gerçekten pazarlık edilebilir, ancak orada. Ve o zamanlar birçoğunun beklediği başka bir yol.
++i
veya i++
ifadesi başka bir ifadede kullanılıyorsa, bunlar arasında geçiş yapmak ifadenin anlambilimini değiştirir, dolayısıyla olası herhangi bir performans kazancı / kaybı söz konusu değildir. Bağımsızlarsa, yani işlemin sonucu hemen kullanılmazsa, iyi bir derleyici onu aynı şeye derler, örneğin bir INC
montaj talimatı.
i++
ve ++i
döngü sabitlerini bir ayarlayarak hemen hemen her durumda birbirinin yerine kullanılabilir, böylece programcı için yaptıkları işte eşdeğerdirler. 2) Her ikisi de aynı talimatı derlemesine rağmen, işlemcileri CPU için farklıdır. Böyle bir durumda i++
, CPU artışı aynı değeri kullanan başka bir talimata paralel olarak hesaplayabilir (CPU'lar bunu gerçekten yapar!), ++i
CPU ile birlikte diğer talimatları zamanlamadan sonra programlamak zorundadır .
if(++foo == 7) bar();
ve if(foo++ == 6) bar();
işlevsel olarak eşdeğerdir. Bununla birlikte, ikincisi bir döngü daha hızlı olabilir, çünkü karşılaştırma ve artış CPU tarafından paralel olarak hesaplanabilir. Bu tek döngü çok önemli değil, ama fark var.
<
örneğin vs gibi <=
) görünür ++
, bu nedenle düşünce arasındaki dönüşüm genellikle kolayca mümkündür.
@Mark Derleyicinin değişkenin (yığın tabanlı) geçici kopyasını optimize etmesine izin verilse de ve gcc (son sürümlerde) bunu yapıyor olsa da, tüm derleyicilerin her zaman bunu yapacağı anlamına gelmez .
Sadece mevcut projemizde kullandığımız derleyicilerle test ettim ve 4'ten 3'ü optimize etmiyor.
Özellikle derhal daha hızlı, ancak daha yavaş olan kodun okunması kolay değilse, derleyicinin doğru olduğunu varsaymayın.
Kodunuzdaki operatörlerden birinin gerçekten aptalca bir uygulaması yoksa:
Alwas ++ i i ++ 'ı tercih eder.
C dilinde, derleyici genellikle sonuç kullanılmazsa bunları aynı olacak şekilde optimize edebilir.
Ancak, C ++ 'da kendi ++ işleçlerini sağlayan diğer türler kullanılıyorsa, önek sürümünün postfix sürümünden daha hızlı olması muhtemeldir. Bu nedenle, postfix semantiğine ihtiyacınız yoksa, önek operatörünü kullanmak daha iyidir.
Postfix önek artış daha yavaş olduğu bir durum düşünebilirsiniz:
Kayıtlı bir işlemcinin A
akümülatör olarak kullanıldığını ve birçok talimatta kullanılan tek kayıt olduğunu düşünün (bazı küçük mikrodenetleyiciler aslında böyle).
Şimdi aşağıdaki programı ve bunların varsayımsal bir düzene dönüştürülmesini hayal edin:
Önek artışı:
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]
Postfix artışı:
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]
Değerinin nasıl b
yeniden yüklenmeye zorlandığına dikkat edin. Önek artışı ile, derleyici sadece değeri arttırabilir ve kullanmaya devam edebilir, muhtemelen istenen değer artımdan sonra kayıtta olduğundan, yeniden yüklemekten kaçının. Ancak, postfix artışı ile, derleyicinin biri eski diğeri artan değeri olmak üzere iki değerle uğraşması gerekir, bu da yukarıda gösterdiğim gibi bir bellek erişimine neden olur.
Tabii ki, artışın değeri tek bir i++;
ifade gibi kullanılmazsa, derleyici postfix veya önek kullanımından bağımsız olarak bir arttırma talimatı oluşturabilir (ve yapar).
Bir yan not olarak, içinde bir ifadenin herhangi bir ek çaba olmadan bir ifadeye b++
dönüştürülemeyeceğinden bahsetmek istiyorum ++b
(örneğin a ekleyerek - 1
). Bu yüzden ikisini bir ifadenin parçasıysa karşılaştırmak gerçekten geçerli değildir. Genellikle, kullanamayacağınız b++
bir ifadenin içinde kullandığınız yerde ++b
, ++b
potansiyel olarak daha verimli olsa bile , bu sadece yanlış olur. Tabii ki, ifade bunun için yalvarıyorsa (örneğin a = b++ + 1;
, değiştirilebilir a = ++b;
).
Buradaki cevapların çoğunu ve yorumların çoğunu okudum ve nerede daha etkili (ve şaşırtıcı bir şekilde daha etkili ) olduğunu düşünebildiğim tek bir örneğe başvurmadım . Bu DEC PDP-11 için C derleyicileri için!i++
++i
--i
i--
PDP-11, bir kaydın azaltılması ve arttırma sonrası montaj talimatlarına sahipti, ancak bunun tersi değildi. Talimatlar, herhangi bir "genel amaçlı" kaydın bir yığın işaretçisi olarak kullanılmasına izin verdi. Yani böyle bir şey kullandıysanız *(i++)
, tek bir montaj talimatında derlenebilir, ancak *(++i)
yapamazsınız.
(Veya demeliyim Bu tabii ki bir çok ezoterik örnektir, ama sonradan arttırma daha etkilidir istisna sağlamaz oldu bu gün PDP-11 C kodu için fazla talep olmadığından,).
--i
ve i++
.
Her zaman ön artışı tercih ederim, ancak ...
Ben işleç ++ işlev çağrıldığında bile, işlev satır içine alırsa derleyici geçici optimize mümkün olacağını işaret etmek istedim. ++ işleci genellikle kısa olduğundan ve genellikle üstbilgide uygulandığından, satır içine alma olasılığı yüksektir.
Dolayısıyla, pratik amaçlar için, iki formun performansı arasında büyük bir fark yoktur. Bununla birlikte, her zaman ön artışı tercih ederim, çünkü anlamaya çalıştığım optimize ediciye güvenmek yerine söylemeye çalıştığım şeyi doğrudan ifade etmek daha iyi görünüyor.
Ayrıca, optimize ediciye daha az şey yapması derleyicinin daha hızlı çalıştığı anlamına gelir.
C'm biraz paslı, bu yüzden şimdiden özür dilerim. Hızla sonuçları anlayabiliyorum. Ancak, her iki dosyanın da aynı MD5 karmasına nasıl geldiği konusunda kafam karıştı. Belki bir for döngüsü aynı çalışır, ancak aşağıdaki 2 kod satırı farklı montaj üretmez mi?
myArray[i++] = "hello";
vs
myArray[++i] = "hello";
Birincisi, değeri diziye yazar, sonra i değerini artırır. İkinci i artışları daha sonra diziye yazar. Ben montaj uzmanı değilim, ama sadece aynı yürütülebilir kod bu 2 farklı kod satırı tarafından oluşturulacak görmüyorum.
Sadece iki sentim.
foo[i++]
için foo[++i]
açıkçası programı semantiğini değiştirecek başka bir şey değiştirmeden, ancak bazı işlemciler üzerinde bir döngü kaldırma optimizasyon mantığı olmadan bir derleyici kullanırken, artan p
ve q
örneğin gerçekleştirdiği bir döngü çalışan daha sonra bir kez ve *(p++)=*(q++);
daha hızlı olan gerçekleştirdiği bir döngü kullanmaktan daha olacaktır *(++pp)=*(++q);
. Bazı işlemcilerdeki çok sıkı döngüler için hız farkı önemli olabilir (% 10'dan fazla), ancak muhtemelen C'de artışın artışın önünden daha hızlı olduğu tek durumdur.