`if`'leri Yukarı, `for`'ları Aşağı Taşımak
(matklad.github.io)- Bir fonksiyon içindeki if ifadelerini çağrı noktasına taşımak, kodun karmaşıklığını azaltmaya yardımcı olur
- Koşul kontrolleri ve dallanma işlemleri tek bir yerde toplandığında, tekrarları ve gereksiz dal kontrollerini fark etmek kolaylaşır
- enum çözme refaktörü kullanılarak aynı koşulun kodun farklı yerlerine yayılması önlenebilir
- Toplu işlem temelli
fordöngüleri, performans artışı ve yinelemeli işlerin optimize edilmesinde etkilidir ifyukarı,foraşağı deseni birlikte kullanıldığında, kodun okunabilirliği ve verimliliği aynı anda artırılabilir
Birbiriyle ilişkili iki kural hakkında kısa not
- Bir fonksiyon içinde if koşul ifadesi varsa, bunun fonksiyon çağrı noktasına taşınıp taşınamayacağını düşünmek önerilir
- Örnekte olduğu gibi, önkoşulu fonksiyon içinde denetlemektense, bu denetimi çağrı noktasına bırakmak ya da önkoşulu türler (veya
assert) ile garanti altına almak daha uygundur - Önkoşul denetimlerini yukarı taşıma (Push up) yaklaşımı, kodun genelini etkileyerek toplamda gereksiz koşul kontrolü sayısını azaltma etkisi yaratır
Denetim akışı ve koşul ifadelerinin merkezileştirilmesi
- Denetim akışı ve if ifadeleri, kod karmaşıklığı ve hataların başlıca nedenlerindendir
- Koşul ifadelerini çağrı noktası gibi üst seviyelerde toplayıp dallanma işlemlerini tek bir fonksiyonda yoğunlaştırmak, asıl işi ise doğrusal (straight-line) alt yordamların yapmasına bırakmak yararlı bir desendir
- Dallanma ve denetim akışı tek bir yerde toplandığında, tekrarlanan dallar ve gereksiz koşullar daha kolay fark edilir
Örnek:
ffonksiyonu içinde iç içe geçmişififadeleri olduğunda, ölü kodu (Dead Branch) fark etmek daha kolaydır- Dallanma birden fazla fonksiyona (
g,h) dağıtıldığında bunu görmek zorlaşır
enum çözme refaktörü (Dissolving enum Refactor)
- Kod aynı koşullu dallanmayı
enumgibi yapılar içinde barındırıyorsa, koşulu üst seviyeye çekerek dallanma ile işi daha net ayırmak mümkündür - Bu yaklaşım uygulandığında, aynı koşulun kod içinde birden çok kez tekrar etmesi engellenebilir
Örnek:
- Aynı dallanma koşulunun
f,gfonksiyonlarında veenum Eiçinde ayrı ayrı ifade edildiği bir durum, - tek bir üst seviye koşul dallanmasıyla kodun genelinde sadeleştirilebilir
Veri odaklı düşünme (Data Oriented Thinking) ve toplu işlemler
- Programların çoğu birden çok nesneyle (varlıkla) çalışır. Kritik yolun (Hot Path) performansı genellikle çok sayıdaki nesnenin işlenmesiyle belirlenir
- Toplu işleme (
batch) kavramını benimseyerek, nesne kümeleri üzerindeki işlemleri temel yaklaşım haline getirmek ve tekil nesne işlemlerini özel durum olarak ele almak daha uygundur
Örnek:
-
frobnicate_batch(walruses)gibi bir toplu işleme fonksiyonunu temel almak, -
tek tek nesneleri ise bir
fordöngüsüyle işlenen özel duruma dönüştürmek mümkündür -
Bu yaklaşım, performans optimizasyonunda önemli rol oynar; büyük hacimli işlerde başlangıç maliyetini düşürür ve sıra esnekliğini artırır
-
SIMD işlemlerinden (
struct-of-arrayvb.) da yararlanılabilir; belirli alanlar topluca işlendiikten sonra tüm işlem sürdürülebilir
Pratik örnekler ve önerilen desen
- FFT tabanlı polinom çarpımı gibi durumlarda, birden çok noktada eşzamanlı hesaplamayı mümkün kılarak performans en üst düzeye çıkarılabilir
- Koşul ifadelerini yukarı, döngüleri aşağı taşıma kuralı birlikte uygulanabilir
Örnek:
- Aynı koşul ifadesini döngü içinde sürekli denetlemek yerine, koşulu döngünün dışına çıkarmak döngü içindeki dallanmayı azaltır ve optimizasyon ile vektörleştirmeyi kolaylaştırır
- Bu yaklaşım, TigerBeetle tasarımı gibi büyük ölçekli sistemlerin veri düzleminde de yüksek verimlilik sağlar
Sonuç
ififadelerini (üst seviye: çağrı noktası, denetim katmanı) yukarıya,fordöngülerini (alt seviye: işlem katmanı, veri işleme katmanı) aşağıya taşıma desenini birleştirmek; kodun okunabilirliğini, verimliliğini ve performansını birlikte iyileştirebilir- Soyut vektör uzayı bakış açısından düşünmek (küme düzeyinde işlemler), tekrarlayan dallanma işleminden daha iyi bir problem çözme aracıdır
- Kısacası:
ifyukarı,foraşağı!
1 yorum
Hacker News görüşü
ifkoşulu fonksiyonun içindeyse, bunu çağırana taşıyıp taşıyamayacağını düşün” tavsiyesine çok fazla karşı örnek var. Fonksiyon 37 yerde çağrılıyorsa ne olacak, her çağrı noktasında aynıififadesini mi tekrar edeceğiz? Meselagetaddrinfoya daEnterCriticalSectiongibi fonksiyonlara da böyleif’i dışarı taşı demek mümkün mü? Bence bu dönüşüm ancak en fazla birkaç yerden çağrılan ve bu kararın fonksiyonun ilgi alanı dışında olduğu durumlarda düşünülebilir. Bir yöntem, yalnızca koşulu değerlendiren bir fonksiyonun yardımcı bir fonksiyona işi devretmesi olabilir. Ve koşulu döngünün dışına taşımak gerektiğinde, çağıran taraf daha alt seviyedeki koşul yardımcısını doğrudan kullanabilir. Ama bu tartışmanın özü “optimizasyon”. Optimizasyon çoğu zaman daha iyi program tasarımıyla çatışıyor. Çağıranın koşulu bilmesine gerek olmaması daha iyi tasarım olabilir. Bu ikilem OOP’de de sık görülür. “if” ile temsil edilen kararın gerçekte metot dispatch’i ile yapıldığı durumlar vardır. Bu dispatch’i döngünün dışına çıkarmak da tasarım ilkeleriyle sürtüşebilir. Örneğin bir canvas’a görüntü çizerken her seferindeputpixelçağırmak yerineblitgibi bir yöntem kullanmak buna örnektirEnterCriticalSectiongibi bir fonksiyon için giriş noktasında güçlü doğrulama yapmak, buna koşullar da dahil, doğrudur. Ama uygulama kodundaif’i çağıran tarafa taşımak sorun olmayabilir. Kütüphane ya da çekirdek kodda denetim akışını kenarlara taşımak uygundur. Kendi çalıştığınız alan içinde denetim akışını kenarlarda tutmak iyidir. Ama bu tür kurallar her zaman yalnızca deyimsel yaklaşımlardır; bağlama uygun biçimde mantıklı karar verebilen biri tarafından duruma göre uygulanmalıdırmatchifadesi, çok biçimli bir metot çağrısıyla değiştirilebilir. Bu yaklaşımın amacı, ilk koşul ayrımının belirlendiği an ile gerçek davranışın çalıştırıldığı anı ayırmaktır. Durum ayrımı nesnenin içinde (burada enum değeri) ya da closure’da taşındığı için, bunu her çağrıda tekrar etmeye gerek kalmaz. Durum ayrımı değişirse yalnızca ayrım noktasını değiştirirsiniz; gerçek davranışın gerçekleştiği yerleri düzenlemek gerekmez. Bunun bedeli ise her duruma ait davranış dallarını doğrudan görebilmenin rahatlığı ile kod düzeyinde durum listesine bağımlılık oluşması arasındaki ödünleşimdirififadelerini aşağı itmeyi seven araçlar. Ama bu yazı tam tersine,ififadelerini yukarıya, yani daha üst seviye fonksiyonlara taşımayı öneriyor. Böylece karmaşık dallanma mantığını tek bir fonksiyonda merkezileştirebilir, gerçek somut işi ise alt yordamlarına devredebilirsinizif (weShouldDoThis()) { doThis(); }gibi; her kontrolü ayrı bir fonksiyona çıkarırsanız test etmek ve karmaşıklığı yönetmek kolaylaşırfordöngüsünün içindekiif,fordöngüsünün dışındakiif’ten daha kolay yönetilebilir ve bellek erişimi açısından daha verimli olabilir. Somut bir örnek olarak, tek sayıysa+1, çift sayıysa-2yapan bir işlem düşünün; normalde her döngü adımında dallanma gerekir ama SIMD ile vektörleştirirseniz 16 adetintdeğerini aynı anda işleyebilir ve hiç branch kullanmazsınız. Derleyici doğru şekilde vektörleştirebilirse, özgün kodu dallanmasız optimize bir sürüme dönüştürürforiçindekiif, veriye bağlı bir dallanma olduğu için yukarı taşımak kolay değil. Eğer algoritmaif (length % 2 == 1) { ... } else { ... }gibi döngü dışı bir koşul kullanıyor olsaydı, böyle bir koşulu elbettefor’un üstüne taşımak doğru olurdu. SIMD sürümünde iseiftamamen ortadan kalkıyor; bence bu, yazarın da seveceği ideal kod kalıbıfordöngüsünde öğe değerine göre dallanan kod geldi. Derleyicilerin böyle kodu otomatik vektörleştirmesi ne kadar zor, bilen var mı? Sınırın nerede olduğunu merak ediyorumforiçi dallanma, “forkuraldır, dallanma ise davranış” gibi bir soyutlama yaratıyor. Ama yeni gereksinimler geldiğinde bu soyutlama bozuluyor ve zorla parametre ekleyerek ya da istisna işleme artırarak kodu anlaması güç bir hale getiriyorsunuz. Başta soyutlama kurmadan yazılmış olsaydı sonuç daha açık ve bakımı daha kolay olurdu. https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction