Önemsiz olmayan koşullu ifadeler, döngülerin başlangıç ​​bölümüne taşınmalı mıdır?


21

Bu fikri stackoverflow.com adresinde aldım.

Aşağıdaki desen yaygındır:

final x = 10;//whatever constant value
for(int i = 0; i < Math.floor(Math.sqrt(x)) + 1; i++) {
  //...do something
}

Yapmaya çalıştığım nokta koşullu ifade karmaşık bir şey ve değişmez.

Bunu, döngünün başlangıç ​​bölümünde belirtmek daha doğru olur mu?

final x = 10;//whatever constant value
for(int i = 0, j = Math.floor(Math.sqrt(x)) + 1; i < j; i++) {
  //...do something
}

Bu daha açık mı?

Koşullu ifade gibi basit ise ne

final x = 10;//whatever constant value
for(int i = 0, j = n*n; i > j; j++) {
  //...do something
}

47
Neden sadece döngüden önce çizgiye taşımak değil , o zaman sen de mantıklı bir isim verebilirsin.
jonrsharpe

2
@Mehrdad: Eşdeğer değiller. Eğer xbüyüklük içinde büyük, Math.floor(Math.sqrt(x))+1eşittir Math.floor(Math.sqrt(x)). :-)
R ..

5
@ jonrsharpe Çünkü bu değişkenin kapsamını genişletirdi. Bunu yapmamak için iyi bir sebep olduğunu söylemiyorum , ancak bazı insanların yapmamasının nedeni budur.
Kevin Krumwiede

3
@KevinKrumwiede Kapsam bir endişe ise, kodu kendi bloğuna yerleştirerek sınırlandırın, örneğin, { x=whatever; for (...) {...} }veya daha iyisi, ayrı bir işlev olması için yeterli bir işlem olup olmadığına bakın.
Blrfl

2
@ jonrsharpe Ayrıca init bölümünde de bildirirken makul bir isim verebilirsiniz. Onu oraya koyacağımı söylememek; ayrı ise okumak daha kolaydır.
JollyJoker,

Yanıtlar:


62

Ne yapardım böyle bir şey:

void doSomeThings() {
    final x = 10;//whatever constant value
    final limit = Math.floor(Math.sqrt(x)) + 1;
    for(int i = 0; i < limit; i++) {
         //...do something
    }
}

Dürüst olmak gerekirse, başlatmanın j(şimdi limit) döngü başlığına tıklanmasının tek iyi nedeni, onu doğru şekilde kapsam içinde tutmaktır. Olmayan bir sorun yapmak için gereken tüm güzel bir sıkı kapsama alanı.

Hızlı olma arzusunu takdir edebilirim ama okunabilirliği gerçekten iyi bir sebep olmadan feda etmeyin.

Tabi, derleyici optimize edebilir, çoklu varyasyonları başlatmak yasal olabilir, ancak döngüler olduğu gibi hata ayıklamak için yeterince zordur. Lütfen insanlara karşı nazik olun. Bu gerçekten bizi yavaşlatıyor gibi görünüyorsa onu düzeltecek kadar iyi anlamak güzel.


İyi bir nokta. Ben mesela, ifade şey basitse başka değişkenle rahatsız değil varsayılabilir for(int i = 0; i < n*n; i++){...}atamak olmaz n*nyapacağınız bir değişkene?
Celeritas,

1
Yapardım ve yapardım. Fakat hız için değil. Okunabilirliği için. Okunabilen kod kendi başına hızlı olma eğilimindedir.
candied_orange

1
Kapsam belirleme konusu bile işaretlerinizin sabit olması halinde gider ( final). Derleyici uygulamasının değişmesini önleyen bir sabite işlevin daha sonra ulaşılabilir olup olmadığını kim umursar?
jpmc26

Bence, olmasını beklediğiniz şey büyük bir şey. Örneğin sqrt kullandığını biliyorum ama ya başka bir işlevse? İşlev saf mı? Hep aynı değerleri mi bekliyorsun? Yan etkileri var mı? Taraflar, her yinelemede olmasını istediğiniz bir şeyi etkiler mi?
Pieter B

38

İyi bir derleyici her iki şekilde de aynı kodu üretecektir, bu nedenle performansa gidecekseniz, yalnızca kritik bir döngüdeyseniz ve aslında onu profillendirdiyseniz ve bir fark yarattığını tespit ettiyseniz, değişiklik yapın. Derleyici bunu optimize edemese bile, insanlar işlev çağrılarıyla ilgili durumdaki yorumlarda da belirtildiği gibi, çoğu durumda, performans farkı bir programcının göz önünde bulundurması için çok küçük olacaktır.

Ancak...

Kuralların öncelikle insanlar arasında bir iletişim aracı olduğunu ve her iki seçeneğinizin de diğer insanlarla çok iyi iletişim kurmadığını unutmamalıyız. Birincisi, her yinelemede ifadenin hesaplanması gerektiği izlenimini verir ve ikincisi, başlangıç ​​bölümünde olmak, döngünün içinde bir yerde güncelleneceği anlamına gelir.

Aslında döngünün üstüne çıkarılmasını finalve kodu okuyan herkes için derhal ve bolca anlaşılır olmasını sağlamak isterdim . Bu, ya değişkenin kapsamını arttırdığı için ideal değildir, ancak çevreleme işleviniz zaten bu döngüden daha fazlasını içermemelidir.


5
İşlev çağrıları dahil etmeye başladığınızda, derleyicinin optimizasyonu çok daha zorlaşır. Derleyici, Math.sqrt'ın hiçbir yan etkisi olmadığı konusunda özel bir bilgiye sahip olmalıdır.
Peter Green,

@PeterGreen Bu Java ise, JVM çözebilir, ancak biraz zaman alabilir.
chrylis

Soru hem C ++ hem de Java olarak etiketlendi. Java'nın JVM'sinin ne kadar gelişmiş olduğunu ve ne zaman ve ne zaman çözebileceğini bilmiyorum, ama genel olarak C ++ derleyicilerinin bunu çözemediğini biliyorum, ancak derlemeye standart olmayan bir ek açıklamaya sahip olmadıkça bir işlev saftır. yani, fonksiyonun gövdesi görünür durumdadır ve dolaylı olarak adlandırılan tüm fonksiyonlar aynı kriterler tarafından saf olarak algılanabilir. Not: Yan efektsiz döngü koşulundan çıkarmak için yeterli değildir. Genel duruma bağlı işlevler, döngü gövdesi durumu değiştirebiliyorsa, döngüden çıkarılamaz.
HVD

Math.sqrt (x) Mymodule.SomeNonPureMethodWithSideEffects (x) ile değiştirilince ilginçleşir.
Pieter B

9

@Karl Bielefeldt'in cevabında dediği gibi, bu genellikle sorun değil.

Bununla birlikte, bir zamanlar C ve C ++ 'da yaygın bir sorun olmuştur ve bir hile, kodu okunabilirliği azaltmadan bu konuyu adım adım atmak için bir numara ortaya çıkmıştır - geriye doğru tekrar eder0 .

final x = 10;//whatever constant value
for(int i = Math.floor(Math.sqrt(x)); i >= 0; i--) {
  //...do something
}

Şimdi her yinelemede şartlı >= 0olan, her derleyicinin 1 veya 2 montaj talimatında derleneceği şarttır . Son birkaç on yılda yapılan her CPU bunun gibi temel kontrollere sahip olmalıdır; x64 makinemde hızlı bir kontrol yaparken bunun tahmin edilebilir şekilde cmpl $0x0, -0x14(%rbp)(uzun-int-karşılaştırma değeri 0'a karşılık rbp offsetted -14) dönüştüğünü ve jl 0x100000f59(önceki karşılaştırma “2nd-arg için doğruysa, döngüyü izleyen talimatlara atladığını” görüyorum. <1-arg ”) .

Not I kaldırıldı + 1arasından Math.floor(Math.sqrt(x)) + 1; Matematiğin çalışabilmesi için başlangıç ​​değeri olmalıdır int i = «iterationCount» - 1. Ayrıca dikkat edilmesi gereken, yineleyicinizin imzalanması gerektiğidir; unsigned intçalışmayacak ve büyük olasılıkla derleyici-uyaracak.

~ 20 yıl boyunca C tabanlı dillerde programlama yaptıktan sonra, şimdi ileriye dönük yineleme yapmak için belirli bir neden olmadıkça, yalnızca ters-dizin-yineleme döngüleri yazıyorum. Koşullardaki daha basit kontrollere ek olarak, ters-yineleme de sıklıkla, yinelenen sırada mutasyona uğrayan dizi mutasyonlarının ne olacağı konusunda yan adımlara yol açar.


1
Yazdığınız her şey teknik olarak doğru. Yönergeler hakkındaki yorum yanıltıcı olabilir, çünkü tüm modern CPU'lar ayrıca kendileri için özel bir talimatı olup olmadıklarına bakılmaksızın, ileri yineleme döngüleriyle iyi çalışacak şekilde tasarlanmıştır. Her durumda, çoğu zaman yinelemenin gerçekleştirilmemesi için genellikle döngü içinde harcanır .
Jørgen Fogh

4
Modern derleyici optimizasyonlarının "normal" kod göz önünde bulundurularak tasarlandığı unutulmamalıdır. Çoğu durumda, derleyici, optimizasyon "hileler" kullanıp kullanmamaya bakılmaksızın en kısa sürede kod üretecektir. Ancak bazı hileler , ne kadar karmaşık olduklarına bağlı olarak optimizasyonu engelleyebilir . Bazı hileler kullanmak, kodunuzu daha okunabilir hale getirir veya genel hataları yakalamanıza yardımcı olursa, bu harikadır, ancak "bu şekilde kod yazarsam daha hızlı olacağını" düşünerek kendinizi kandırmayın. Kodunuzu normalde nasıl yapacağınızı yazın, ardından optimize etmeniz gereken yerleri bulmak için profili yazın.
0x5453

Ayrıca unsigned, kontrolü değiştirirseniz sayaçların burada çalışacağını unutmayın (en kolay yol, her iki tarafa da aynı değeri eklemektir); Örneğin, herhangi bir azaltmaya yönelik Deconay (i + Dec) >= Dechep aynı sonucu olmalıdır signedçek i >= 0hem, signedve unsignedsürece dil için iyi tanımlanmış sarma kuralları vardır olarak, sayaçlar unsigneddeğişkenler (özellikle, -n + n == 0her ikisi için de geçerlidir olmak zorundadır signedve unsigned). Bununla birlikte, >=0derleyici bunun için bir optimizasyona sahip değilse, bunun imzalı bir kontrolden daha az etkili olabileceğini unutmayın .
Justin Time - Monica

1
@JustinTime Evet, imzalı gereksinim en zor kısımdır; DecHem başlangıç ​​değerine hem de bitiş değerine bir sabit eklemek işe yarar ancak daha az sezgiseldir ve idizi dizini olarak kullanıyorsanız unsigned int arrayI = i - Dec;döngü gövdesinde de bir işlem yapmanız gerekir . Ben sadece imzasız bir yineleyici ile takılıyorum zaman ileri-yineleme kullanın; genellikle i <= count - 1mantığı ters-yineleme döngülerine paralel tutmak için bir şartla.
Slipp D. Thompson

1
@ SlippD.Thompson DecBaşlangıç ​​ve bitiş değerlerine özel olarak eklemek istememiştim , ancak durumu Decher iki taraftan da kontrol ederek değiştirmek istedim . for (unsigned i = N - 1; i + 1 >= 1; i--) /*...*/ Bu i, koşulun sol tarafındaki mümkün olan en düşük değerin 0(sargının karışmasını engellemek için) olduğunu garanti ederken döngü içinde normal şekilde kullanmanıza izin verir . Yine de , imzasız sayaçlarla çalışırken ileri yinelemeyi kullanmak kesinlikle çok daha kolay.
Justin Time - Monica

3

Math.sqrt (x) Mymodule.SomeNonPureMethodWithSideEffects (x) ile değiştirilince ilginçleşir.

Temelde benim modus operandi'm şudur: eğer bir şeyin daima aynı değeri vermesi bekleniyorsa, o zaman sadece bir kez değerlendirin. Örneğin List.Count, listenin döngünün çalışması sırasında değişmemesi gerekiyorsa, döngünün dışındaki sayımı başka bir değişkene getirin.

Bu "sayımların" bir kısmı özellikle veritabanlarıyla uğraşırken şaşırtıcı şekilde pahalı olabilir. Listenin yinelenmesi sırasında değişmesi gerekmeyen bir veri kümesi üzerinde çalışıyor olsanız bile.


Sayı pahalı olduğunda, hiç kullanmamalısınız. Bunun yerine, for( auto it = begin(dataset); !at_end(it); ++it )
Ben Voigt

@BenVoigt Bir yineleyici kullanmak, bu işlemler için kesinlikle en iyi yoldur. Yan etkilerle saf olmayan yöntemleri kullanma konusundaki amacımı göstermek için sadece bahsetmiştim.
Pieter B,

0

Bence bu çok dile özgü. Örneğin, eğer C ++ 11 kullanıyorsanız, durum kontrolü bir constexprfonksiyon ise, derleyicinin her seferinde aynı değeri vereceğini bildiği için birden fazla işlemi optimize etme ihtimalinin yüksek olacağından şüpheleniyorum .

Bununla birlikte, eğer işlev çağrısı constexprderleyici olmayan bir kütüphane işlevi ise , hemen hemen kesinlikle her yinelemede uygulayacaktır (inline olmadığı ve bu nedenle saf olarak çıkarılabildiği sürece).

Java hakkında daha az şey biliyorum ama bu JIT'ye derlendiğinde, derleyicinin çalışma zamanında muhtemel satır içi ve durumu iyileştirmek için yeterli bilgiye sahip olduğunu tahmin ediyorum. Fakat bu iyi bir derleyici tasarımına bağlı olacaktır ve bu döngüyü belirleyen derleyici, yalnızca tahmin edebileceğimiz bir optimizasyon önceliğidir.

Şahsen, eğer mümkünse for döngüsünün içine konmanın konulmasının biraz daha zarif olduğunu hissediyorum, ancak eğer karmaşıksa bunu bir constexprveya inlinefonksiyona yazacağım ya da dilleriniz, işlevin saf ve iyimser olduğunu ima edecek kadar eşdeğer. Bu, amacı belirgin hale getirir ve büyük bir okunamayan çizgi oluşturmadan deyimsel döngü stilini korur. Ayrıca, eğer kendi işlevi varsa, durum kontrolüne bir isim verir, böylece okuyucular karmaşıksa okumadan kontrolün ne olduğunu mantıklı bir şekilde görebilirler.

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.