Neden dev bir "switch" ifadesi yerine OO yaklaşımı kullanmalı?


59

Bir .Net, C # mağazasında çalışıyorum ve kodumuzda dev Switch deyimlerini daha fazla nesne yönelimli yaklaşımdan ziyade birçok "Case" ile kullanmamız konusunda ısrarcı bir iş arkadaşım var. Argümanı sürekli olarak bir Switch ifadesinin bir "cpu atlama tablosu" nu derlediği ve bu nedenle en hızlı seçenek olduğu gerçeğine dayanıyor (başka şeylerde ekibimize hız umurumuzda olmadığını söylese bile).

Gerçekten buna karşı bir tartışmam yok ... çünkü neden bahsettiğini bilmiyorum.
O haklı mı?
Sadece kıçından mı konuşuyor?
Sadece burada öğrenmeye çalışıyorum.


7
Derleme koduna bakmak ve "cpu atlama tablosu" aramak için .NET Reflector gibi bir şey kullanarak haklı olup olmadığını doğrulayabilirsiniz.
SinirliFormsDesigner ile

5
?.. Cpu atlama tablosu "Anahtarı ifadesi a derler '' Bu yüzden sanal fonksiyonlar sadece doğrudan bağlantılıdır Yok tüm saf-sanal fonksiyonları ile sevk kötü durum yöntemini yapar karşılaştırmak için herhangi bir kod terk ettiniz
S.Lott

64
Makineler için değil, İNSANLAR için kod yazılmalıdır, aksi takdirde montajda her şeyi yaparız.
maple_shaft

8
Eğer o kadar çok büyük bir şeref ise, Knuth'a şöyle bir teklifte bulunun: "Küçük verimleri unutmalıyız, zamanın% 97'sini söyleyelim: erken optimizasyon tüm kötülüklerin kaynağıdır."
DaveE

12
İdame. Size yardımcı olabileceğim bir tek kelimeyle cevaplanmış başka sorularınız var mı?
Matt Ellen

Yanıtlar:


48

Muhtemelen eski bir C korsanı ve evet, kıçından çıkıyor. .Net C ++ değil; Net derleyici daha iyi olmaya devam ediyor ve bugünün bir sonraki .Net versiyonunda olmasa da çoğu akıllı saldırı karşı-üretken. Küçük fonksiyonlar tercih edilir çünkü .Net JIT'ler kullanılmadan önce her fonksiyon bir kezdir. Öyleyse, bazı vakalar bir programın bir Yaşam Döngüsü sırasında hiçbir zaman isabet almazsa, bunları JIT'de derlemenin bir maliyeti yoktur. Her neyse, eğer hız bir sorun değilse, optimizasyonlar yapılmamalıdır. Derleyici ikinci, ilk önce programcı için yaz. İş arkadaşınız kolayca ikna olmayacak, bu yüzden ampirik olarak daha iyi organize edilmiş kodun gerçekten daha hızlı olduğunu kanıtlarım. En kötü örneklerinden birini seçer, daha iyi bir şekilde yeniden yazar ve kodunuzun daha hızlı olduğundan emin olurdum. Gerekirse Cherry-pick. Ardından birkaç milyon kez çalıştırın, onu profilleyin ve gösterin.

DÜZENLE

Bill Wagner yazdı:

Madde 11: Küçük Fonksiyonların Cazibesini Anlayın (Etkili C # İkinci Basım) C # kodunuzu makinenin çalıştırılabilir koduna çevirmenin iki aşamalı bir işlem olduğunu unutmayın. C # derleyicisi, meclislerde teslim edilen IL'yi üretir. JIT derleyicisi gerektiğinde her bir yöntem için (veya satır içindeyken bir grup yöntem) makine kodu oluşturur. Küçük fonksiyonlar JIT derleyicisinin bu maliyeti amorti etmesini çok kolaylaştırır. Küçük fonksiyonların satır içi için aday olma olasılıkları daha yüksektir. Sadece küçüklük değil: Basit kontrol akışı da aynı derecede önemlidir. İşlevlerin içindeki daha az kontrol dalı, JIT derleyicisinin değişkenleri kaydetmesini kolaylaştırır. Daha net bir kod yazmak sadece iyi bir uygulama değildir; çalışma zamanında nasıl daha verimli kod oluşturursunuz.

EDIT2:

Öyleyse ... görünüşe göre bir switch ifadesi bir grup if / else ifadesinden daha hızlı ve daha iyidir, çünkü bir karşılaştırma logaritmik ve diğeri doğrusaldır. http://sequence-points.blogspot.com/2007/10/why-is-switch-statement-faster-than-if.html

Büyük bir switch ifadesini değiştirmedeki en sevdiğim yaklaşım, kendilerine cevaben çağrılan fonksiyonlarla değerleri eşleştiren bir sözlükle (ya da enumsiyonları veya küçük girişleri çalıştırıyorsam bile bir dizi). Bunu yapmak, bir sürü iğrenç paylaşılan spagetti halini uzaklaştırmaya zorlar, ancak bu iyi bir şey. Büyük bir anahtar ifadesi genellikle bir bakım kabusu. Öyleyse ... diziler ve sözlüklerle, arama sabit bir zaman alacaktır ve fazladan fazla hafıza boşa harcanacaktır.

Yine de switch ifadesinin daha iyi olduğuna ikna olmadım.


47
Daha hızlı kanıtlamak için endişelenme. Bu erken optimizasyon. Tasarruf edebileceğiniz milisaniye, size 200ms'ye mal olacak veritabanına eklemeyi unuttuğunuz endekse kıyasla hiçbir şey değildir. Yanlış savaşta savaşıyorsun.
Rein Henrichs

27
@Job ya gerçekten haklıysa? Mesele, onun yanlış olması değil, mesele onun haklı olmasıdır ve önemli değil .
Rein Henrichs

2
Davaların% 100'ünde haklı olsa bile, halen vaktimizi boşa harcıyor.
Jeremy

6
Bağladığın o sayfayı okumaya çalışırken gözlerimi oymak istiyorum.
AttackHobo

3
C ++ nefretinin nesi var? C ++ derleyicileri de daha iyi hale geliyor ve büyük anahtarlar C # 'da olduğu gibi C # da olduğu kadar kötü ve aynı sebeple. Eğer sizi üzen eski C ++ programcıları tarafından kuşatılmışsanız, bunun nedeni C ++ programcıları oldukları için değil, kötü programcıları oldukları için.
Sebastian Redl

39

Meslektaşınız, bu değişikliğin tüm başvuru ölçeğinde gerçek bir ölçülebilir fayda sağladığına dair kanıt sağlayamazsa , bu, gerçekten böyle bir fayda sağlayan yaklaşımınızdan (yani polimorfizm) daha düşüktür: sürdürülebilirlik.

Mikrooptimis sadece darboğazlar sabitlendikten sonra yapılmalıdır . Erken optimizasyon tüm kötülüklerin köküdür .

Hız ölçülebilir. "A yaklaşımı B yaklaşımından daha hızlı" konusunda çok az yararlı bilgi var. Soru “ Ne kadar hızlı? ”.


2
Kesinlikle doğru. Asla bir şeyin daha hızlı olduğunu iddia etme , daima ölç. Ve sadece uygulamanın bu kısmı performans darboğazı olduğunda ölçün.
Kilian Foth

6
-1 "Erken optimizasyon tüm kötülüklerin köküdür." Lütfen tüm alıntıyı gösterin, Knuth’un görüşünü önyargılı yapan sadece bir bölümü değil.
alternatif olarak

2
@ mathepic: Kasten bunu bir teklif olarak sunmadım. Bu cümle, benim yaratımım olmasa da, benim kişisel düşüncemdir. Her ne kadar c2 den gelen adamların temel bilgeliğin sadece bir kısmını ele aldığı görülüyor.
back2dos

8
@alternative Tam Knuth alıntı "Verimlilik kepçesinin kötüye kullanıma yol açacağına hiç şüphe yok. hata ayıklama ve bakım dikkate alındığında güçlü bir olumsuz etki göz önünde bulundurulur. Küçük verimlilikleri unutmalıyız, zamanın% 97'sini söyleyelim: erken optimizasyon tüm kötülüklerin kaynağıdır. ” OP'nin iş arkadaşını mükemmel şekilde anlatıyor. IMHO back2dos, alıntıyı "erken optimizasyonun tüm kötülüğün
kökenidir

2
Zamanın @MarkJ% 97
alternatif

27

Daha hızlı olup olmadığını kim umursar?

Gerçek zamanlı yazılım yazmıyorsanız, tamamen çılgınca bir şekilde bir şey yapmaktan alabileceğiniz eksi hız miktarının müşteriniz için çok fazla fark yaratması muhtemel değildir. Bunu hız cephesinde savaşmaya bile gitmezdim, bu adam açıkça konuyla ilgili herhangi bir tartışmayı dinlemeyecek.

Bununla birlikte, bakımın amacı oyunun amacıdır ve dev bir anahtar ifadesi bile hafifçe korunamaz durumdadır, koddaki farklı yolları yeni çocuklara nasıl açıklarsınız? Dokümantasyon kodun kendisi kadar uzun olacak!

Ayrıca, daha sonra etkili bir şekilde test etmedeki yetersizliği elde ettiniz (olası arabirim eksikliğinden söz etmeyerek çok fazla yol var).

[İlgilenmekte olan tarafta: JITter daha küçük yöntemlerde daha iyi performans gösteriyor, bu yüzden dev anahtar deyimleri (ve onların büyük ölçüde büyük yöntemleri) büyük montajlarda IIRC'de hızınıza zarar verecek.]


1
+ büyük bir erken optimizasyon örneği.
ShaneC

Kesinlikle bu.
DeadMG

'Dev bir anahtar ifadesi bile biraz idare edilebilir değil' için +1
Korey Hinton

2
Dev bir anahtar ifadesi, yeni çocuğun kavrayabilmesi için çok daha kolaydır: olası tüm davranışlar orada güzel ve düzenli bir şekilde toplanır. Dolaylı aramaları takip etmek son derece zordur, en kötü durumda (işlev işaretçisi), doğru imza işlevleri için tüm kod tabanını aramanız gerekir ve sanal çağrılar sadece biraz daha iyidir (doğru ad ve imza işlevlerini arayın ve devralma ile ilgili). Ancak sürdürülebilirlik salt okunur olmakla ilgili değildir.
Ben Voigt

14

Anahtar deyiminden uzak dur ...

Bu tür bir switch ifadesi veba gibi kapatılmalıdır çünkü Açık Kapalı Prensibi ihlal ediyor . Ekibi, sadece yeni kod eklemek yerine, yeni işlevsellikler eklenmesi gerektiğinde mevcut kodda değişiklikler yapmaya zorlar.


11
Bu bir ihtar ile geliyor. İşlemler (işlevler / yöntemler) ve türleri vardır. Yeni bir işlem eklediğinizde, switch ifadeleri için kodu yalnızca bir yerde değiştirmeniz gerekir (switch ifadesiyle yeni bir işlev ekleyin), ancak bu yöntemi OO durumundaki tüm sınıflara eklemeniz gerekir (açık ihlal eder) / kapalı ilke). Yeni türler eklerseniz, her switch ifadesine dokunmanız gerekir, ancak OO durumunda yalnızca bir sınıf daha eklersiniz. Bu nedenle, bilinçli bir karar vermek için mevcut türlere daha fazla işlem ekleyip ekleyemeyeceğinizi bilmeniz gerekir.
Scott Whitlock

3
Bir OO paradigmasındaki mevcut türlere OCP'yi ihlal etmeden daha fazla işlem eklemeniz gerekirse, ziyaretçi modelinin bunun için olduğuna inanıyorum.
Scott Whitlock

3
@Martin - yapacaksanız isim çağırın ama bu iyi bilinen bir tradeoff. RC Martin tarafından Temiz Kod'a atıfta bulunuyorum. Yukarıda özetlediklerimi açıklayan OCP hakkındaki makalesini tekrar gözden geçirdi. Gelecekteki tüm gereksinimler için aynı anda tasarım yapamazsınız. Daha fazla işlem veya daha fazla tür ekleme olasılığının yüksek olduğu bir seçim yapmanız gerekir. OO, türlerin eklenmesini tercih eder. İşlemleri sınıf olarak modelliyorsanız, daha fazla işlem eklemek için OO'yu kullanabilirsiniz, ancak bu, kendi sorunları olan (özellikle ek yükü olan) ziyaretçi biçimine giriyor gibi görünmektedir.
Scott Whitlock

8
@ Martin: Hiç ayrıştırıcı yazdın mı? Gözetleme tamponunda bir sonraki simgeyi açan büyük anahtar durumlarına sahip olmak oldukça yaygındır. Sen olabilir sanal fonksiyon sonraki belirteci çağrıları olanlar anahtarları yerine, ama bu maintainence kabus olurdu. Nadirdir, ancak bazen durum çok daha iyi bir seçenektir, çünkü birbirine yakın bir yerde okunması / değiştirilmesi gereken kodu tutar.
nikie

1
@Martin: "Asla", "hiç" ve "Poppycock" gibi kelimeler kullandın, bu yüzden istisnasız tüm davalardan bahsettiğimi sanıyordum, sadece en yaygın dava değil. (Ve BTW: İnsanlar hala elle ayrıştırıcılar yazma Örneğin, CPython ayrıştırıcı hala elle yazılır, IIRC..)
Nikie

8

Büyük anahtar ifadeleriyle manipüle edilen büyük sonlu durum makinesi olarak bilinen kabustan kurtuldum. Daha da kötüsü, benim durumumda, FSM üç C ++ DLL'sini kullandı ve kodun C ile yazılmış bir kişi tarafından yazıldığı oldukça açıktı.

İlgilenmeniz gereken metrikler:

  • Değişiklik yapma hızı
  • Problemi ortaya çıktığında bulma hızı

Bana bu DLL grubuna yeni bir özellik ekleme görevi verildi ve yönetimi, 3 DLL dosyasını bir yama gibi benim için uygun bir nesne yönelimli DLL olarak yeniden yazmamın uzun süreceği konusunda beni ikna ettim. ve jüri, çözümü orada bulunanların içine yerleştirdi. Yeniden yazma, büyük bir başarıydı, çünkü sadece yeni işlevselliği desteklemiyordu, aynı zamanda genişletilmesi de çok kolaydı. Aslında, normalde bir şeyi kırmadığınızdan emin olmak için bir hafta sürecek olan bir iş birkaç saat sürer.

Peki yürütme zamanları? Hız artışı ya da azalması olmadı. Adil olmak gerekirse, performansımız sistem sürücüleri tarafından boğuldu, bu yüzden nesne yönelimli çözüm aslında daha yavaş olsaydı bunu bilemeyiz.

Bir OO dili için devasa anahtar ifadelerinde yanlış olan ne?

  • Program kontrol akışı ait olduğu nesneden uzağa alınır ve nesnenin dışına yerleştirilir.
  • Harici kontrol noktalarının çoğu, gözden geçirmeniz gereken birçok yere çevrilir
  • Durumun nerede saklandığı belli değil, özellikle anahtar bir döngü içindeyse
  • En hızlı karşılaştırma hiç kıyaslama yapmaz (iyi bir nesne yönelimli tasarımla birçok karşılaştırmaya gerek kalmaz)
  • Nesnelerinizde yineleme yapmak ve her nesneyle aynı yöntemi çağırmak, türünüzü kodlayan nesne türünü veya enum'u temel alarak kodunuzu değiştirmekten daha etkilidir.

8

Performans tartışmasını satın almıyorum; hepsi kod bakımları ile ilgili.

ANCAK: bazen , dev bir anahtar ifadesinin sürdürülmesi (daha az kod), soyut bir temel sınıfın sanal işlevlerini geçersiz kılan bir grup küçük sınıftan daha kolaydır. Örneğin, bir CPU emülatörü uygulayacak olsaydınız, her bir komutun işlevselliğini ayrı bir sınıfta uygulamazdınız - sadece daha karmaşık komutlar için yardımcı fonksiyonlar çağırarak, opcode üzerindeki dev bir anahtarın içine yerleştirirdiniz.

Temel kural: eğer anahtar bir şekilde TYPE üzerinde gerçekleştiriliyorsa, muhtemelen devralma ve sanal işlevleri kullanmanız gerekir. Anahtar, sabit tipte bir VALUE (örneğin yukarıdaki talimat opcode) üzerinde gerçekleştiriliyorsa, olduğu gibi bırakmanız tamamdır.


5

Beni ikna edemezsin:

void action1()
{}

void action2()
{}

void action3()
{}

void action4()
{}

void doAction(int action)
{
    switch(action)
    {
        case 1: action1();break;
        case 2: action2();break;
        case 3: action3();break;
        case 4: action4();break;
    }
}

Şundan önemli ölçüde daha hızlı:

struct IAction
{
    virtual ~IAction() {}
    virtual void action() = 0;
}

struct Action1: public IAction
{
    virtual void action()    { }
}

struct Action2: public IAction
{
    virtual void action()    { }
}

struct Action3: public IAction
{
    virtual void action()    { }
}

struct Action4: public IAction
{
    virtual void action()    { }
}

void doAction(IAction& actionObject)
{
    actionObject.action();
}

Ek olarak, OO versiyonu daha da bakımlıdır.


8
Bazı şeyler için ve daha az miktarda eylem için, OO sürümü çok daha etkili. Bir değeri bir IAction yaratmaya dönüştürmek için bir tür fabrikaya sahip olması gerekir. Çoğu durumda bunun yerine sadece bu değeri açmak çok daha okunur.
Zan Lynx

@Zan Lynx: Argümanınız çok genel. IAction nesnesinin oluşturulması, eylem tamsayısını geri almak kadar kolay, zor değildir. Bu yüzden genel bir yol olmadan gerçek bir konuşma yapabiliriz. Bir hesap makinesi düşünün. Buradaki karmaşıklığın farkı nedir? Cevap sıfır. Tüm eylemlerin önceden oluşturduğu gibi. Girdiyi kullanıcıdan alırsınız ve bu işlem zaten bir işlemdir.
Martin York

3
@Martin: Bir GUI hesap makinesi uygulaması varsayıyorsunuz. Bunun yerine gömülü bir sistemde C ++ ile yazılmış bir klavye hesap makinesi uygulaması alalım. Artık bir donanım kayıt cihazından gelen bir tarama kodu tamsayısı var. Şimdi daha az karmaşık olan nedir?
Zan Lynx

2
@Martin: Tamsayı -> arama tablosu -> yeni nesne yaratma -> çağrı sanal fonksiyonunun tamsayı -> anahtar -> fonksiyonundan daha karmaşık olduğunu görmüyor musunuz? Bunu nasıl göremezsin?
Zan Lynx

2
@ Martin: Belki yaparım. Bu arada, IAction nesnesine arama tablosu olmayan bir tamsayıdan action () işlevini nasıl çağırmasını sağladığınızı açıklayın .
Zan Lynx

4

Elde edilen makine kodunun muhtemelen daha verimli olacağı konusunda haklı. Derleyici esası, bir anahtar ifadesini nispeten az komut olacak bir dizi teste ve dallara dönüştürür. Daha soyutlanmış yaklaşımlardan kaynaklanan kodun daha fazla talimat gerektirmesi ihtimali yüksek.

Ancak , özel uygulamalarınızın bu tür bir mikro-optimizasyon için endişelenmesine gerek olmadığı veya .net'i ilk etapta kullanmayacağınız kesin bir durum. Çok kısıtlanmış gömülü uygulamalardan ya da CPU yoğun işlerden yoksun kalan her şey için derleyicinin daima optimizasyonla uğraşmasına izin vermelisiniz. Temiz ve bakımı kolay kod yazmaya odaklanın. Bu neredeyse yürütme süresinde bir nano saniyenin onda birinden daha büyük bir değere sahip.


3

Switch ifadeleri yerine sınıfları kullanmanın başlıca nedenlerinden biri, switch ifadelerinin çok sayıda mantığı olan büyük bir dosyaya yol açma eğiliminde olmasıdır. Bu hem bir bakım kabusu hem de kaynak yönetimi ile ilgili bir problemdir, çünkü farklı bir küçük sınıf dosyaları yerine bu büyük dosyayı kontrol edip düzenlemelisiniz


3

OOP kodundaki bir anahtar ifadesi, eksik sınıfların güçlü bir göstergesidir

iki yolu da deneyin ve bazı basit hız testlerini çalıştırın; Şanslar arasındaki fark önemli değildir. Eğer bunlarsa ve kod zaman açısından kritikse , switch deyimini saklayın


3

Normalde "erken optimizasyon" kelimesinden nefret ederim, ama bu tam anlamıyla. Knuth'un bu ünlü alıntıyı goto, kritik alanlarda kodu hızlandırmak için ifadeleri kullanmaya itme bağlamında kullandığını belirtmekte fayda var . Anahtar bu: kritik yollar.

gotoKodu hızlandırmak için kullanmayı , ancak eleştirel olmayan kod için önsezilere ve batıl inançlara dayanarak bu tür şeyler yapmak isteyen programcılara karşı uyarıda bulunmasını önerdi .

Bir kod temeli boyunca switchmümkün olduğu kadar eşit bir şekilde ifadeleri tercih etmek (herhangi bir ağır yükün işlenip yüklenmediğine bakılmaksızın), Knuth'un "optimize edilmiş" durumlarını korumak için çaba harcayarak tüm gününü harcayan "kuruşlu ve palavra" aptal programcısı olarak adlandırdığı klasik bir örnektir. msgstr "" Pennies'i pound'lardan kurtarmaya çalışmaktan kaynaklanan bir hata ayıklama kabusuna dönüşen kod. Böyle bir kod, ilk etapta bile verimli olmasa bile nadiren korunur.

O haklı mı?

Çok temel verimlilik perspektifinden doğrudur. Bilgime göre hiçbir derleyici nesneleri ve dinamik gönderimi içeren polimorfik kodu bir switch ifadesinden daha iyi optimize edemez. Polimorfik kodlardan gelen sıralı koda bir LUT veya atlama tablosu ile asla bitmeyeceksiniz, çünkü böyle bir kod derleyici için optimize edici bir bariyer işlevi görür (dinamik işlevin hangi zamana kadar çağrılacağını bilemez) ) meydana gelir.

Bu maliyeti zıplama tabloları olarak düşünmemek daha iyidir, ancak optimizasyon engeli açısından daha fazladır. Polimorfizm için, çağrı Base.method()derleyicinin methodsanalsa, kapalı değilse ve geçersiz kılınabiliyorsa hangi işlevin çağrılacağını bilmesine izin vermez . Hangi fonksiyonun önceden çağrılacağını bilmediğinden, fonksiyon çağrısını optimize edemez ve optimizasyon kararları almak için daha fazla bilgi kullanamaz, çünkü aslında hangi fonksiyonun çağrılacağını bilmez. Kodun derlendiği saat.

En iyi duruma getiriciler, bir işlev çağrısına bakabildiklerinde ve arayanı ve arayanı tamamen düzleştiren veya en azından arayanı arayanla en verimli şekilde çalışacak şekilde optimize edecek optimizasyonlar yaptıkları zaman ellerinden gelenin en iyisini yaparlar. Önceden hangi işlevin çağrılacağını bilmiyorlarsa bunu yapamazlar.

Sadece kıçından mı konuşuyor?

Genellikle para birimlerine denk gelen bu maliyetin, bunu tek tip bir şekilde uygulanan bir kodlama standardına dönüştürmek için haklı gösterilmesi, özellikle genişletilebilirlik ihtiyacı olan yerler için çok aptalcadır. Gerçek prematüre optimize edicilerle dikkat etmek istediğiniz en önemli şey budur: küçük performans kaygılarını, her ne kadar olursa olsun bakımdan bağımsız olarak, bir kod tabanı boyunca aynı şekilde uygulanan kodlama standartlarına dönüştürmek isterler.

Ben onlardan biri olduğum için kabul edilen cevapta kullanılan "eski C hacker" alıntıya biraz alıyorum. Çok sınırlı bir donanımdan başlayarak yıllarca kodlayan herkes erken bir optimizasyona dönüşmedi. Yine de bunlarla da karşılaştım ve çalıştım. Ancak bu türler asla dal yanlışlığı veya önbellek özlüyor gibi şeyleri ölçmezler, daha iyi bildiklerini düşünürler ve verimsizlik kavramlarını, bugün gerçek olmayan ve bazen de asla doğrulanmayan batıl inançlara dayanan karmaşık bir üretim kod tabanında kullanırlar. Performans açısından kritik alanlarda gerçekten çalışmış kişiler genellikle etkili optimizasyonun etkin önceliklendirme olduğunu anlarlar ve para biriktirmek için korunabilirliği azaltan bir kodlama standardı oluşturmayı denemek çok etkili olmaz.

Pennies, çok sıkı, performans açısından kritik bir döngüde milyar kez denilen çok fazla iş yapmayan ucuz bir işleve sahip olduğunuzda önemlidir. Bu durumda, 10 milyon doları biriktirdik. Vücudun sadece binlerce dolara mal olduğu, iki kez denen bir işleve sahip olduğunuzda tıraş paraları değmez. Araba satın alırken zamanınızı para kazanmak için harcayacaksınız akıllıca değil. Bir üreticiden milyonlarca kutu soda satın alıyorsanız, para kazanmaya değer. Etkili optimizasyonun anahtarı, bu maliyetleri uygun bağlamda anlamaktır. Her bir satın alımda para biriktirmeye çalışan ve ne satın alırsa alsın, herkesin para kazanmaya kalkıştığını söyleyen biri yetenekli bir optimizer değil.


2

İş arkadaşınız performans konusunda çok endişeli görünüyor. Bazı durumlarda büyük bir kasa / anahtar yapısı daha hızlı performans gösterebilir, ancak umarım siz OO ve anahtar / kasa versiyonlarında zamanlama testleri yaparak bir deney yaparsınız. OO versiyonunun daha az kodlu olduğunu ve takip edilmesi, anlaşılması ve bakımı daha kolay olduğunu tahmin ediyorum. Öncelikle OO sürümü için tartışacağım (bakım / okunabilirlik başlangıçta daha önemli olması gerektiği için) ve yalnızca anahtar / vaka sürümünü göz önünde bulundurun, ancak OO sürümünde ciddi performans sorunları varsa ve bir anahtar / durumun önemli gelişme.


1
Zamanlama testleriyle birlikte, bir kod dökümü C ++ (ve C #) yönteminin gönderiminin nasıl çalıştığını göstermeye yardımcı olabilir.
S.Lott

2

Hiç kimsenin bahsetmediği polimorfizmin bir sürdürülebilirlik avantajı, her zaman aynı davalar listesine giriyorsanız, kalıtım kullanarak kodunuzu çok daha güzel bir şekilde yapılandırabilmenizdir; değil

Örneğin. Eğer arasında geçiş ise Dog, Catve Elephant, bazen Dogve Cataynı durum var, bunları hem soyut sınıfından miras yapabilir DomesticAnimalve soyut sınıfta olanlar işlevi koydu.

Ayrıca, birkaç kişinin polimorfizmi kullanmayacağınız bir örnek olarak ayrıştırıcı kullanmasına şaşırdım. Ağaç benzeri bir çözümleyici için bu kesinlikle yanlış bir yaklaşımdır, ancak montaj gibi, her bir çizginin biraz bağımsız olduğu bir şeye sahipseniz ve hattın geri kalanının nasıl yorumlanması gerektiğini belirten bir opcode ile başlayın, tamamen polimorfizm kullanacağım ve bir fabrika. Her sınıf ExtractConstantsveya gibi fonksiyonları uygulayabilir ExtractSymbols. Bu yaklaşımı oyuncak BASIC tercümanı olarak kullandım.


Bir anahtar, varsayılan durumuyla da davranışları devralabilir. "... BaseOperationVisitor'ı genişletiyor", "varsayılan: BaseOperation (düğüm)" olur
Samuel Danielson

0

“Küçük etkinlikleri unutmalıyız, zamanın yaklaşık% 97'sini söyleyelim: erken optimizasyon tüm kötülüklerin kaynağıdır”

Donald Knuth


0

Bu sürdürülebilirlik için kötü olmasa bile, performans için daha iyi olacağına inanmıyorum. Sanal işlev çağrısı yalnızca bir ekstra dolaylı işlemdir (bir switch ifadesi için en iyi durumla aynı), bu nedenle C ++ 'ta bile performans kabaca eşit olmalıdır. Tüm işlev çağrılarının sanal olduğu C # 'da, her iki versiyonda da aynı sanal işlev çağrısına sahip olduğunuz için switch ifadesinin daha kötü olması gerekir.


1
"Yok" mu eksik? C # değil tüm işlev çağrıları sanaldır. C # Java değil.
Ben Voigt

0

Meslektaşınız zıplama masalarıyla ilgili yorumların yapıldığı sürece arka tarafında konuşmuyor. Ancak, yanlış kod yazmayı haklı çıkarmak için bunu kullanmak yanlış gittiği yerdir.

C # derleyicisi anahtar ifadelerini birkaç durumla if / else dizisine dönüştürür, if / else kullanmaktan daha hızlı değildir. Derleyici, daha büyük anahtar ifadelerini bir Sözlüğe (meslektaşınızın bahsettiği atlama tablosu) dönüştürür. Lütfen daha fazla ayrıntı için konu hakkındaki Yığın Taşması sorusuna verilen cevaba bakınız .

Büyük bir switch ifadesinin okunması ve bakımı zordur. "Vaka" ve fonksiyonların sözlüğü okumak çok daha kolaydır. Anahtarın ne hale geldiğine göre, siz ve meslektaşınıza doğrudan sözlükleri kullanmanız tavsiye edilir.


0

Mutlaka kıçından konuşmuyor. En azından C ve C ++ switchifadeleri, yalnızca bir temel işaretçiye erişimi olan bir işlevde dinamik bir gönderiyle gerçekleştiğini görmemişken, tabloları atlamak için optimize edilebilir. En azından ikincisi, sanal bir işlev çağrısından ne tür bir alt işaretçi / referans üzerinden kullanıldığını tam olarak belirlemek için daha çevreleyen koda bakarak daha akıllı bir optimizer gerektirir.

Bunun da ötesinde, dinamik gönderim genellikle bir "optimizasyon bariyeri" olarak işlev görür, yani derleyici genellikle satır içi kodları sıralayamaz ve yığın dökülmelerini en aza indirmek için kayıtları ideal bir şekilde tahsis edemez, çünkü ne olduğunu çözemez. Sanal işlev, satır içi yapmak ve tüm optimizasyon sihrini yapmak için temel göstergeden çağrılacak. Optimize edicinin o kadar akıllı olmasını ve dolaylı işlev çağrılarını en iyi duruma getirmeyi denemek istediğinizden bile emin değilim, çünkü potansiyel olarak belirli bir çağrı yığınında ayrı ayrı oluşturulan kod bölümlerine yol açabilir (çağrıların foo->f()alacağı bir işlev Arayandan tamamen farklı bir makine kodu üretmek içinbar->f() bir temel işaretçi vasıtasıyla ve bu işlevi çağıran işlev, daha sonra iki veya daha fazla kod sürümü üretmesi gerekirdi ve bunun gibi - üretilen makine kodunun miktarı patlayıcı olurdu - belki de bir JIT izinde çok kötü değil sıcak yürütme yollarını izleyerek anında kodu oluşturur).

Bununla birlikte, birçok cevap yankılandığından, switchbazı marjinal miktarlarla daha hızlı bir şekilde olsa bile, kaygısız ifadeleri tercih etmek kötü bir nedendir . Ayrıca, mikro verimler söz konusu olduğunda, dallanma ve iç içe geçme gibi şeyler, bellek erişim düzenleri gibi şeylere kıyasla genellikle oldukça düşük önceliklidir.

Bu, alışılmadık bir cevap ile buraya atladı dedi. switchİfadelerin polimorfik bir çözüme göre sürdürülebilmesi için bir vaka yapmak istiyorum , ancak bunu yalnızca, yalnızca gerçekleştirilmesi gereken tek bir yer olacağından emin olduğunuzda bilirsiniz switch.

En önemli örnek, merkezi bir olay işleyicisidir. Bu durumda, genellikle yalnızca bir taneyle ilgili olayları işleyen çok fazla yeriniz yoktur (neden "merkezidir"). Bu gibi durumlarda, polimorfik bir çözümün sağladığı genişletilebilirlikten yararlanamazsınız. Polimorfik bir çözüm, analog switchifadeyi yapacak birçok yer olduğunda faydalıdır . Sadece bir tane olacağından eminseniz, switch15 vakadan oluşan bir ifade, geçersiz kılınan işlevlere sahip 15 alt tip tarafından devralınan bir temel sınıf tasarlamak ve bunları başlatmak için bir fabrika, daha sonra sadece bir işlevde kullanılmaktan çok daha basit olabilir. tüm sistemde. Bu gibi durumlarda, yeni bir alt tür eklemek case, bir işleve ifade eklemekten çok daha sıkıcıdır . Bir şey olursa, performans için değil sürdürülebilirlik için tartışırım.switch ne olursa olsun genişletilebilirlikten faydalanmadığınız bu tuhaf durumdaki ifadeler.

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.