Postfix Artış Operatöründen Kaçının


25

Postfix artış operatöründen performans nedenlerinden dolayı kaçınmam gerektiğini (bazı durumlarda) okudum .

Fakat bu kod okunabilirliğini etkilemiyor mu? Bana göre:

for(int i = 0; i < 42; i++);
    /* i will never equal 42! */

Şundan daha iyi görünüyor:

for(int i = 0; i < 42; ++i);
    /* i will never equal 42! */

Ama bu muhtemelen sadece alışkanlık dışında. Kuşkusuz, pek fazla kullanım görmedim ++i.

Bu durumda okunabilirliği feda etmek o kadar kötü mü? Yoksa sadece körüm ve ++ibundan daha okunaklı i++mıyım?


1
i++Performansı etkileyebileceğini bilmeden önce kullandım ++i, bu yüzden değiştirdim. İlk başta ikincisi biraz tuhaf görünüyordu, ama bir süre sonra buna alıştım ve şimdi olduğu kadar doğal hissediyor i++.
gablin

15
++ive i++belli bağlamlarda farklı şeyler yapın, aynı olduklarını varsaymayın.
Orbling

2
C ya da C ++ ile mi ilgili? Onlar çok farklı iki dil! :-) C ++ 'da aptal for-loop'tur for (type i = 0; i != 42; ++i). Sadece operator++aşırı yüklenmekle kalmaz, aynı zamanda operator!=ve de olabilir operator<. Ön ek artış postfix'den daha pahalı değildir, eşit değil daha düşük değerden daha pahalı değildir. Hangilerini kullanmalıyız?
Bo Persson

7
++ C olarak adlandırılmamalı mı?
Armand

21
@Stephen: C ++ C'yi alır, ekler ve eskisini kullanır .
supercat,

Yanıtlar:


58

Gerçekler:

  1. i ++ ve ++ i okumak eşit derecede kolaydır. Birinden hoşlanmıyorsunuz çünkü buna alışık değilsiniz, fakat aslında yanlış yorumlayabileceğiniz hiçbir şey yok, bu yüzden okumak ya da yazmak için daha fazla iş yok.

  2. En azından bazı durumlarda, postfix operatörü daha az verimli olacaktır.

  3. Bununla birlikte,% 99,99'luk durumlarda, önemli değil çünkü (a) zaten basit veya ilkel bir tipe etki edecek ve büyük bir nesneyi kopyalarsa (b) performansta olmayacaksa, sadece bir sorun Kodun (c) kritik kısmı, derleyicinin onu optimize edip etmeyeceğini bilmiyor, yapabilir.

  4. Bu nedenle, özellikle postfix'e ihtiyaç duymuyorsanız önek kullanılmasını öneriyorum, çünkü (a) diğer şeylerle hassas olmak için iyi bir alışkanlık ve (b) mavi ayda bir postfix kullanmak istediğinizde ve yanlış yoldan geçin: her zaman ne demek istediğinizi yazarsanız, bu daha az olasıdır. Performans ve optimizasyon arasında her zaman bir denge vardır.

Sağduyu kullanmalı ve gerekene kadar mikro-optimizasyon yapmamalısınız, ancak bunun uğruna hiçbir şekilde yetersiz kalmalısınız. Tipik olarak bu şu anlama gelir: Öncelikle, kritik olmayan kodlarda bile kabul edilemez derecede verimsiz olan herhangi bir kod yapımını ekarte edin (normalde, sebepsiz yere 500 MB'lık nesnelerin sebepsiz yere iletilmesi gibi temel bir kavramsal hatayı temsil eden bir şey); ve ikincisi, kodu yazmanın diğer yollarından en netini seçin.

Ancak, burada cevabın basit olduğuna inanıyorum: özellikle postfix gerekmediği sürece önek yazmanın (a) çok marjinal olarak daha net ve (b) daha marjinal olarak daha verimli olması daha muhtemel olduğuna inanıyorum, bu yüzden her zaman varsayılan olarak yazmalısınız. eğer unutursan endişelenme.

Altı ay önce, seninle aynı olduğunu düşündüm, i ++ daha doğaldı, ama tamamen alışkın olduğun şeydi.

EDIT 1: Genel olarak bu konuda güvendiğim "Daha Etkili C ++" 'da Scott Meyers, postfix operatörünü kullanıcı tanımlı türlerde kullanmaktan kaçınmanız gerektiğini söylüyor (çünkü postfix artırma fonksiyonunun tek mantıklı uygulaması Nesnenin kopyasını alın, artımı gerçekleştirmek için ön ek artış işlevini çağırın ve kopyayı döndürün, ancak kopyalama işlemleri pahalı olabilir).

Bu nedenle, (a) bugün bunun doğru olup olmadığı hakkında genel kurallar olup olmadığını bilmiyoruz, (b) aynı zamanda (daha az) gerçek türlere (c) uygulanıp uygulanmadığını (+) şimdiye kadar hafif bir yineleyici sınıftan daha fazlası. Ama yukarıda tarif ettiğim tüm nedenlerden ötürü, farketmez, daha önce söylediklerimi yap.

EDIT 2: Bu genel pratiği ifade eder. Bazı özel durumlarda önemli olduğunu düşünüyorsanız, o zaman onu profiline bakmalı ve görmelisiniz. Profil yapmak kolay ve ucuzdur ve çalışır. En iyi duruma getirilmesi gereken ilk prensiplerden düşmek zor ve pahalıdır ve işe yaramaz.


İlanınız para üzerinde. İnfix + işlecinin ve artım sonrası ++ 'ın aClassInst = someOtherClassInst + henüzAnotherClassInst ++ gibi aşırı yüklendiği ifadelerde, çözümleyici, artım sonrası işlemi gerçekleştirmek için kodu oluşturmadan önce katkı işlemini gerçekleştirmek için kod oluşturur; geçici bir kopya yarat. Buradaki performans katili artım sonrası değildir. Aşırı yüklenmiş bir infix operatörünün kullanılmasıdır. Infix operatörleri yeni örnekler üretir.
bit twiddler

2
Bunun sebebi, insanların 'kullanmak için' olduklarını ben çok şüpheli i++ziyade ++içünkü bu soru / cevap başvurulan belli popüler programlama dilinin adının olduğunu ...
Gölge

61

Her zaman önce programlayıcıyı ve bilgisayarı ikinci olarak kodlayın.

Derleyici Kodunuzdaki üzerinde uzman göz döküm etti, sonra bir performans farkı, varsa VE bunu ölçebilir VE sonra bunu değiştirebilirsiniz - fark eder.


7
SUPERB deyimi !!!
Dave

8
@ Martin: bu yüzden ön ek artış kullanmalıyım. Postfix anlambilimi eski değerin etrafta tutulması anlamına gelir ve eğer buna gerek yoksa, o zaman kullanmak yanlış olur.
Matthieu M.

1
Daha net olacak bir döngü endeksi için - ancak bir göstergeyi artırarak bir diziyi yinelemeyi ve öneki kullanmayı, bir performans artışına bakılmaksızın kötü olacak, başlamadan önce yasadışı bir adreste başlamak anlamına geliyorsa
Martin Beckett

5
@Matthew: Artış sonrası eski değerin bir kopyasını almayı gerektirdiği doğru değil. Bir derleyici çıktısını görüntüleyene kadar bir derleyicinin ara değerleri nasıl kullandığından emin olunamaz. Açıklamalı GCC tarafından oluşturulan assembly dili girişimi görüntülemek için zaman ayırırsanız, GCC'nin her iki döngü için de aynı makine kodunu oluşturduğunu göreceksiniz. Artış sonrası ön artış tercih edilmesine ilişkin bu saçmalık, çünkü daha verimli olması varsayımdan biraz daha fazladır.
bit-twiddler

2
@Mathhieu: Gönderdiğim kod optimizasyon kapalıyken üretildi. C ++ belirtimi, bir derleyicinin artım sonrası kullanıldığında geçici bir değer örneği üretmesi gerektiğini belirtmez. Yalnızca, artırma öncesi ve sonrası operatörlerin önceliğini belirtir.
bit twiddler

13

GCC, her iki döngü için de aynı makine kodunu üretir.

C kodu

int main(int argc, char** argv)
{
    for (int i = 0; i < 42; i++)
            printf("i = %d\n",i);

    for (int i = 0; i < 42; ++i)
        printf("i = %d\n",i);

    return 0;
}

Meclis Kodu (yorumlarımla birlikte)

    cstring
LC0:
    .ascii "i = %d\12\0"
    .text
.globl _main
_main:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    subl    $36, %esp
    call    L9
"L00000000001$pb":
L9:
    popl    %ebx
    movl    $0, -16(%ebp)  // -16(%ebp) is "i" for the first loop 
    jmp L2
L3:
    movl    -16(%ebp), %eax   // move i for the first loop to the eax register 
    movl    %eax, 4(%esp)     // push i onto the stack
    leal    LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
    movl    %eax, (%esp)      // push the address of the format string onto the stack
    call    L_printf$stub    // call printf
    leal    -16(%ebp), %eax  // make the eax register point to i
    incl    (%eax)           // increment i
L2:
    cmpl    $41, -16(%ebp)  // compare i to the number 41
    jle L3              // jump to L3 if less than or equal to 41
    movl    $0, -12(%ebp)   // -12(%ebp) is "i" for the second loop  
    jmp L5
L6:
    movl    -12(%ebp), %eax   // move i for the second loop to the eax register 
    movl    %eax, 4(%esp)     // push i onto the stack
    leal    LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
    movl    %eax, (%esp)      // push the address of the format string onto the stack
    call    L_printf$stub     // call printf
    leal    -12(%ebp), %eax  // make eax point to i
    incl    (%eax)           // increment i
L5:
    cmpl    $41, -12(%ebp)   // compare i to 41 
    jle L6               // jump to L6 if less than or equal to 41
    movl    $0, %eax
    addl    $36, %esp
    popl    %ebx
    leave
    ret
    .section __IMPORT,__jump_table,symbol_stubs,self_modifying_code+pure_instructions,5
L_printf$stub:
    .indirect_symbol _printf
    hlt ; hlt ; hlt ; hlt ; hlt
    .subsections_via_symbols

Optimizasyon açıkken nasıl?
serv-inc

2
@ user: Muhtemelen değişiklik yok, ancak bit-twiddler'ın yakında herhangi bir zamanda geri dönmesini bekliyor musunuz?
Deduplicator

2
Kendine iyi bak: C'de aşırı yüklenmiş operatörlerle kullanıcı tanımlı türler yokken, C ++ 'da var ve temel türlerden kullanıcı tanımlı türlere genelleme geçersiz .
Deduplicator

@Deduplicator: Bu cevabın kullanıcı tanımlı türlere genellemediğini de belirttiğiniz için teşekkür ederiz. Sormadan önce kullanıcı sayfasına bakmamıştım.
serv-inc

12

Performans konusunda endişelenmeyin, zamanın% 97'sini söyleyin. Erken Optimizasyon tüm Kötülüklerin Köküdür.

- Donald Knuth

Şimdi bu bizim yolumuzdan çıktı, hadi seçiminizi akılcı bir şekilde yapalım :

  • ++i: ön ek artış , mevcut değerin artırılması ve sonuç
  • i++: postfix artımı , değeri kopyala, mevcut değeri arttır, kopyayı verir

Eski değerin bir kopyası gerekli olmadıkça, postfix artışını kullanmak işleri halletmenin en iyi yoludur.

Yanlışlık tembellikten gelir, daima niyetinizi en doğrudan şekilde ifade eden yapıyı kullanın, gelecekteki sağlayıcının asıl amacınızı yanlış anlayabileceğinden daha az şansı vardır.

Burada (gerçekten) küçük olmasına rağmen, kod okuyarak gerçekten şaşırdığım zamanlar var: Gerçekten niyetin ve gerçek ifadenin çakışıp çakışmadığını ve elbette, birkaç ay sonra, onlar mı (yoksa) merak ediyorum. ya da hatırlamadım ...

Bu yüzden size doğru bakıp bakmamasının önemi yok. Embrace KISS . Birkaç ay içinde eski uygulamalarınızdan vazgeçeceksiniz.


4

C ++ içinde olabilir katılan operatör aşırı yükler varsa sen yazma şablonu kod ve yinelemeler geçirilen ne olabileceğini bilmiyoruz, özellikle önemli bir performans fark yaratır. Herhangi yineleyici X arkasındaki mantık hem önemli ve olarak önemli- olabilir yani, derleyici tarafından yavaş ve kullanım dışıdır.

Ancak bu, sadece önemsiz bir tür olacağını bildiğiniz C'deki durum değildir ve performans farkı önemsizdir ve derleyici kolayca en iyi duruma getirilebilir.

Öyleyse bir ipucu: Siz C veya C ++ dilinde programlıyorsunuz ve sorular ikisi ile değil, biri veya diğeri ile ilgilidir.


2

Her iki işlemin de performansı temelde yatan mimariye bağlıdır. Birinin bellekte depolanan bir değeri arttırması gerekir; bu, von Neumann darboğazının her iki durumda da sınırlayıcı faktör olduğu anlamına gelir.

++ i durumunda, biz zorundayız

Fetch i from memory 
Increment i
Store i back to memory
Use i

İ ++ durumunda, zorundayız

Fetch i from memory
Use i
Increment i
Store i back to memory

++ ve - operatörleri, kökenlerini PDP-11 komut kümesine kadar izler. PDP-11 bir sicil üzerinde otomatik artımlı artış yapabilir. Ayrıca, kayıtta bulunan etkili bir adrese otomatik olarak azaltma da yapabilir. Her iki durumda da, derleyici bu makine düzeyinde işlemlerden yalnızca, söz konusu değişken bir "kayıt" değişkeni olduğunda faydalanabilir.


2

Bir şeyin yavaş olup olmadığını bilmek istiyorsanız, test edin. Bir BigInteger veya eşdeğeri alın, her iki deyimi kullanarak döngüye benzer şekilde yapıştırın, döngünün iç kısmının optimize edilmediğinden emin olun ve ikisini de zamanlayın.

Makaleyi okuduktan sonra üç nedenden dolayı ikna edici bulmuyorum. Birincisi, derleyici, hiç kullanılmamış bir nesnenin yaratılması çevresinde optimizasyon yapabilmelidir. İkincisi, bu i++kavram döngüler için nümerik için deyimseldir , bu yüzden gerçekten etkilendiğini görebildiğim durumlar sınırlıdır. Üçüncüsü, onu destekleyecek numara olmadan, tamamen teorik bir argüman sağlarlar.

Özellikle 1. numaralı sebebe dayanarak, benim tahminim, zamanlamayı gerçekten yaptığınızda, onların hemen yanlarında olacağıdır.


-1

Her şeyden önce IMO okunabilirliğini etkilemez. Görmeye alıştığınız şey bu değildi, ancak alışmaya alışmanız çok kısa sürdü.

İkincisi, kodunuzda bir ton postfix operatörü kullanmıyorsanız büyük olasılıkla bir fark görmeyeceksiniz. Mümkün olduğunda bunları kullanmama konusundaki ana argüman, orijinal var değerinin bir kopyasının orijinal var olanın hala kullanılabileceği argümanların sonuna kadar saklanması gerektiğidir. Bu, mimariye bağlı olarak 32 bit veya 64 bit olabilir. Bu 4 veya 8 bayt veya 0.00390625 veya 0.0078125 MB'ye eşittir. Çok uzun bir süre boyunca kaydetmeniz gereken tonlarca kullanmazsanız, bugünün bilgisayar kaynakları ve hızı ile postfix'den prefix'e geçiş yaparken fark bile görmeyeceğiniz ihtimaller çok yüksektir.

EDIT: Sonuçların yanlış olduğu kanıtlandığından kalan kısmı unutun (++ i ve i ++ bölümleri her zaman aynı şeyi yapmıyor ... hala geçerli).

Ayrıca, aynı şeyleri bir davada yapmadıklarını daha önce belirtmekteydi. Karar verirseniz, geçişi yaparken dikkatli olun. Hiç denemedim (her zaman postfix kullandım) bu yüzden kesin olarak bilmiyorum ama postfix'den prefix'e geçmenin farklı sonuçlar doğuracağını düşünüyorum: (yine hatalı olabilirim ... derleyiciye bağlı / tercüman da)

for (int i=0; i < 10; i++) //the set of i values here will be {0,1,2,3,4,5,6,7,8,9}
for (int i=0; i < 10; ++i) //the set of i values here will be {1,2,3,4,5,6,7,8,9,10}

4
Artış işlemi for döngüsünün sonunda gerçekleşir, bu nedenle tam olarak aynı çıkışa sahip olurlar. Derleyici / tercümana bağlı değildir.
jsternberg

@jsternberg ... Teşekkürler Bu artışın ne zaman gerçekleştiğinden emin değildim, çünkü denemek için hiçbir neden bulamadım. Üniversitede derleyiciler yaptığımdan bu yana çok uzun zaman geçti! lol
Kenneth

Yanlış yanlış yanlış.
ruohola

-1

Anlamsal olarak düşünüyorum , bundan ++idaha mantıklı i++, bu yüzden birincisine sadık kalacağım, ancak bunu yapmamanın yaygın olması dışında (Java'da olduğu gibi, i++yaygın olarak kullanıldığı için kullanmanız gerekir ).


-2

Bu sadece performansla ilgili değil.

Bazen kopyalamayı uygulamaktan kaçınmak istersiniz, çünkü mantıklı değil. Ön ek artışının kullanılması buna bağlı olmadığından, ön ek formuna sadık kalmak çok kolaydır.

Ve ilkel tipler ve karmaşık tipler için farklı artışlar kullanmak ... bu gerçekten okunamaz.


-2

Gerçekten ihtiyacın yoksa, ++ i'ye sadık kalacağım. Çoğu durumda, amaçlanan budur. Sık sık i ++ 'a ihtiyacınız yoktur ve böyle bir yapı okurken daima iki kez düşünmeniz gerekir. ++ i ile kolaydır: 1 ekler, kullanır ve sonra hala aynıdır.

Bu yüzden, Martin Beckett ile tam anlamıyla aynı fikirdeyim: kendiniz için kolaylaştırın, zaten yeterince zor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.