it-swarm-tr.com

Neden dev bir "switch" deyimi yerine bir OO yaklaşım kullanalım?)

Bir .Net, C # dükkanında çalışıyorum ve kodumuzda daha fazla nesne yönelimli yaklaşımlar yerine birçok "Durum" ile dev Switch ifadeleri kullanmamız gerektiğinde ısrar eden bir iş arkadaşım var. Onun argümanı sürekli olarak bir Switch ifadesinin bir "cpu atlama masası" na derlendiği ve bu nedenle en hızlı seçenek olduğu gerçeğine geri dönüyor (ekibimize diğer şeylerde hızı önemsemediğimiz söylense de).

Dürüst olmak gerekirse buna karşı bir argümanım yok ... çünkü onun neden bahsettiğini bilmiyorum.
Haklı mı?
Sadece kıçını mı konuşuyor?
Sadece burada öğrenmeye çalışıyorum.

61
James P. Wright

Muhtemelen eski bir C hackerı ve evet, kıçından konuşuyor. Net C++ değildir; .Net derleyici daha iyi olmaya devam ediyor ve zeki hack'lerin bugün olmasa bile bir sonraki .Net sürümünde karşı üretken. .Net JIT-ler her işlevi kullanılmadan önce bir kez küçük fonksiyonlar tercih edilir. Bu nedenle, bazı durumlar bir programın Yaşam Döngüsü sırasında hiçbir zaman vurulmazsa, JIT'i derlemek için hiçbir ücret alınmaz. Her neyse, hız bir sorun değilse, optimizasyonlar olmamalıdır. Önce programcı, ikinci derleyici için yazın. İş arkadaşınız kolayca ikna olmayacak, bu yüzden daha iyi organize edilmiş kodun daha hızlı olduğunu ampirik olarak kanıtlayacağım. En kötü örneklerinden birini seçer, daha iyi bir şekilde yeniden yazar ve kodunuzun daha hızlı olduğundan emin olurum. Gerekirse kiraz topla. Sonra birkaç milyon kez çalıştırın, profil verin ve gösterin. Bu ona iyi öğretmeli.

[~ # ~] düzenlemek [~ # ~]

Bill Wagner şunu yazdı:

Madde 11: Küçük İşlevlerin Çekiciliğini Anlama (Etkili C # İkinci Baskı) C # kodunuzu makine tarafından çalıştırılabilir koda çevirmenin iki adımlı bir işlem olduğunu unutmayın. C # derleyicisi montajlarda teslim edilen IL üretir. JIT derleyicisi, gerektiğinde her yöntem (veya satır içi işlem söz konusu olduğunda yöntem grubu) için makine kodu üretir. Küçük fonksiyonlar, JIT derleyicisinin bu maliyeti amorti etmesini çok daha kolay hale getirir. Küçük fonksiyonların da satır içi aday olma olasılığı daha yüksektir. Sadece küçüklük değil: Daha basit kontrol akışı da aynı derecede önemlidir. İşlevler içindeki daha az denetim dalı, JIT derleyicisinin değişkenleri kaydetmesini kolaylaştırır. Daha net kod yazmak sadece iyi bir uygulama değildir; çalışma zamanında nasıl daha verimli kod oluşturacağınız budur.

EDIT2:

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

Eh, büyük bir switch deyimini değiştirmek için en sevdiğim yaklaşım, değerleri onlara yanıt olarak çağrılan işlevlerle eşleyen bir sözlükle (veya bazen numaralandırmaları veya küçük ints'ları açtığımda bir dizi bile). Bunu yapmak, bir sürü kötü paylaşılan spagetti durumunu kaldırmaya zorlar, ancak bu iyi bir şeydir. Büyük bir anahtar ifadesi genellikle bir bakım kabusu. Yani ... diziler ve sözlüklerle, arama sabit bir zaman alacak ve fazladan az bellek harcanacak.

Anahtar ifadesinin daha iyi olduğuna hala ikna olmadım.

50
Job

İş arkadaşınız, bu değişikliğin tüm uygulamanın ölçeğinde gerçek bir ölçülebilir fayda sağladığına dair kanıt sağlayamadıkça, aslında böyle bir fayda sağlayan yaklaşımınızdan (yani polimorfizm) daha düşüktür. : sürdürülebilirlik.

Mikroopsisizasyon sadece yapılmalıdır, sonra darboğazlar sabitlenir. 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ıdır" konusunda çok az yararlı bilgi vardır. Soru " Ne kadar hızlı? ".

39
back2dos

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

Gerçek zamanlı yazılım yazmıyorsanız, tamamen delice bir şey yapmaktan alabileceğiniz ufak bir hızlanma, müşteriniz için çok fark yaratacaktır. Bunu hız cephesinde savaşmaya bile devam etmem, bu adam açıkça konuyla ilgili herhangi bir tartışmayı dinlemeyecek.

Bununla birlikte, sürdürülebilirlik oyunun amacıdır ve dev bir anahtar ifadesi biraz da bakımı mümkün değildir, kod yoluyla farklı yolları yeni bir adama nasıl açıklarsınız? Belgelerin kodun kendisi kadar uzun olması gerekir!

Ayrıca, etkin bir şekilde test etmeyi tam olarak engelleyemediniz (olası birçok yol, olası arabirim eksikliğinden bahsetmemek vb.), Bu da kodunuzu daha az bakım yapılabilir hale getirir.

[İlgilenen taraf: JITter daha küçük yöntemlerde daha iyi performans gösterir, bu nedenle dev anahtar ifadeleri (ve bunların doğası gereği büyük yöntemleri) büyük montajlarda hızınıza zarar verir, IIRC.]

27
Ed James

Anahtar ifadesinden bir adım uzaklaşın ...

Bu tür bir anahtar deyimi veba gibi kapatılmalıdır çünkü Açık Kapalı Prensip ihlal eder. Yeni işlevsellik eklenmesi gerektiğinde, yalnızca yeni kod ekleyerek ekibi mevcut kodda değişiklik yapmaya zorlar.

14
Dakotah North

Performans argümanını satın almıyorum; her şey kod bakımı ile ilgilidir.

AMA: bazen, dev bir anahtar ifadesinin bakımı (daha az kod), soyut bir temel sınıfın sanal işlev (ler) ini 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 talimatın işlevselliğini ayrı bir sınıfta uygulamazsınız - sadece daha karmaşık talimatlar için yardımcı işlevleri çağıran opcode üzerindeki dev bir swtich'e yerleştirirsiniz.

Temel kural: Anahtar bir şekilde TYPE üzerinde gerçekleştirilirse, büyük olasılıkla kalıtım ve sanal işlevleri kullanmalısınız. Anahtar, sabit tipte bir VALUE üzerinde gerçekleştirilirse (örn., Yukarıdaki gibi talimat op kodu), olduğu gibi bırakmak sorun olmaz.

9
zvrba

Büyük anahtar ifadeleri tarafından manipüle edilen masif sonlu durum makinesi olarak bilinen kabustan kurtuldum. Daha da kötüsü, benim durumumda, FSM üç C++ DLL yayıldı ve kod C usta bir kişi tarafından yazılmış oldukça basitti.

İlgilenmeniz gereken metrikler:

  • Değişiklik yapma hızı
  • Sorun oluştuğunda bulma hızı

Bu DLL kümesine yeni bir özellik ekleme görevi verildi ve yönetimi sadece 3 DLL'leri düzgün bir nesne odaklı olarak yeniden yazmak beni alacaktır ikna edebildi DLL benim için yama ya da jüri çözümünü zaten orada olanlara ulaştırırdı.Yeniden yazma, sadece yeni işlevselliği desteklemediğinden, genişletilmesi çok daha kolay olduğu için büyük bir başarıydı. Bir şey kırmadığınızdan emin olmak için bir hafta ayırın ve sonunda birkaç saat sürecektir.

Peki icra sürelerine ne dersiniz? Hız artışı veya azalması olmadı. Adil olmak gerekirse performansımız sistem sürücüleri tarafından kısıtlandı, bu yüzden nesneye yönelik çözüm aslında daha yavaş olsaydı bunu bilemezdik.

OO dil için büyük anahtar ifadelerinde sorun nedir?

  • Program kontrol akışı ait olduğu nesneden alınır ve nesnenin dışına yerleştirilir
  • Harici kontrolün birçok noktası, gözden geçirmeniz gereken birçok yere dönüşür
  • Durumun nerede depolandığı belirsizdir, özellikle anahtar bir döngü içinde ise
  • En hızlı karşılaştırma hiç bir karşılaştırma değildir (iyi bir nesne yönelimli tasarımla birçok karşılaştırma ihtiyacından kaçınabilirsiniz)
  • Nesnelerinizde yineleme yapmak ve her nesneyi her zaman aynı yöntemi çağırmak, kodunuzu nesne türüne veya türü kodlayan numaralamaya göre değiştirmekten daha iyidir.
8
Berin Loritsch

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ıdır:

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();
}

Ayrıca OO sürüm sadece daha sürdürülebilir.

5
Martin York

Ortaya çıkan makine kodunun muhtemelen daha verimli olacağı doğrudur. Derleyici zorunlu bir switch deyimini nispeten az talimat olacak bir dizi test ve dallara dönüştürür. Daha soyutlanmış yaklaşımlardan kaynaklanan kodun daha fazla talimat gerektirme olasılığı yüksektir.

ANCAK : Özel uygulamanızın bu tür bir mikro optimizasyon konusunda endişelenmesi gerekmiyor ya da kullanmazsınız. ilk etapta net. Çok kısıtlı gömülü uygulamaların veya CPU yoğun çalışmanın kısa olduğu herhangi bir şey için derleyicinin her zaman optimizasyonla ilgilenmesine izin vermelisiniz. Temiz ve bakımı kolay kod yazmaya konsantre olun. Bu hemen hemen her zaman, yürütme süresinde bir nano saniyenin onda birinden çok daha büyük bir değere sahiptir.

4
Luke Graham

Switch deyimleri yerine sınıfları kullanmanın başlıca nedenlerinden biri, switch deyimlerinin çok fazla mantığı olan büyük bir dosyaya yol açması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 dosyası yerine bu büyük dosyayı kontrol etmeniz ve düzenlemeniz gerekir

3
Homde

OOP kodundaki bir switch deyimi, eksik sınıfların güçlü bir göstergesidir

her iki şekilde de deneyin ve bazı basit hız testleri yapın; şans fark önemli değildir. Bunlar ve kod zaman açısından kritik ise , switch deyimini saklayın

3
Steven A. Lowe

Normalde Word'den, "erken optimizasyon" dan nefret ederim, ama bu bunu sever. Knuth'un kritik alanlarındaki kodu hızlandırmak için goto ifadelerini kullanmaya itme bağlamında bu ünlü alıntıyı kullandığını belirtmek gerekir. Anahtar budur: kritik yollar.

Kodu hızlandırmak için goto kullanmayı önermişti, ancak kritik olmayan kod için önsezilere ve batıl inançlara dayanarak bu tür şeyleri yapmak isteyen programcılara karşı uyarı yapıyordu.

Bir kod temeli boyunca switch ifadelerini mümkün olduğunca eşit olarak tercih etmek (herhangi bir ağır yükün işlenip işlenmediği) Knuth'un "kuruş bilge ve pound- bütün gün harcamalarını "optimize edilmiş" kodlarını korumak için uğraştıran aptal "programcı, kuruşları poundlar üzerinde kurtarmaya çalışmanın bir sonucu olarak bir hata ayıklama kabusu haline geldi. Böyle bir kod, ilk etapta bile verimli olsa bile nadiren korunur.

Haklı mı?

Çok temel verimlilik açısından doğrudur. Bildiğim hiçbir derleyici, nesneleri ve dinamik dağıtımı içeren polimorfik kodu bir switch deyiminden daha iyi optimize edemez. Hiçbir zaman bir LUT veya atlama tablosu ile polimorfik koddan satır içi koda gitmeyeceksiniz, çünkü bu tür kod derleyici için bir optimizasyon bariyeri görevi görmektedir (dinamik gönderim zamanına kadar hangi işlevi çağıracağını bilmeyecektir) ) meydana gelir.

Bu maliyeti sıçrama tabloları açısından değil, optimizasyon engeli açısından daha fazla düşünmek daha yararlıdır. Çok biçimlilik için, Base.method() çağrısı, method sanal ise, mühürlenmemişse ve geçersiz kılınabiliyorsa, derleyicinin gerçekte hangi işlevin çağrıldığını bilmesine izin vermez. Hangi işlevin gerçekte önceden çağrılacağını bilmediği için, işlev çağrısını optimize edemez ve optimizasyon kararları vermede daha fazla bilgi kullanamaz, çünkü hangi işlevin çağrılacağını bilmiyor kodun derlenme zamanı.

Optimize ediciler, bir işlev çağrısına göz atabildiğinde ve arayanı tamamen düzleştiren ve çağıran optimizasyonlar yapabildiklerinde veya en azından arayanla en verimli şekilde çalışmak için arayanı optimize ettiklerinde en iyi durumdadırlar. Hangi işlevin önceden çağrıldığını bilmiyorlarsa bunu yapamazlar.

Sadece kýçýný mý konuţuyor?

Genellikle penni olan bu maliyetin, bunu tekdüze bir şekilde uygulanan bir kodlama standardına dönüştürmeyi haklı çıkarmak için, özellikle de uzayabilirlik ihtiyacı olan yerler için genellikle çok aptalcadır. Orijinal erken optimize edicilerle dikkat etmek istediğiniz ana şey budur: küçük performans kaygılarını, herhangi bir sürdürülebilirlik gözetmeksizin bir kod tabanı boyunca eşit olarak uygulanan kodlama standartlarına dönüştürmek isterler.

Ben de onlardan biri olduğum için kabul edilen cevapta kullanılan "eski C hacker" teklifine küçük bir suç alıyorum. On yıllardır çok sınırlı bir donanımdan başlayarak kodlayan herkes erken bir optimize ediciye dönüşmedi. Yine de bunlarla karşılaştım ve onunla çalıştım. Ancak bu türler hiçbir zaman şube yanlış tahminleri veya önbellek özledikleri gibi şeyleri ölçmezler, daha iyi bildiklerini düşünürler ve verimsizlik kavramlarını, bugün doğru olmayan ve bazen asla doğru olmayan batıl inançlara dayanan karmaşık bir üretim kod tabanına dayandırırlar. Performans açısından kritik alanlarda gerçekten çalışan insanlar genellikle etkili optimizasyonun etkili önceliklendirme olduğunu anlar ve kuruşları kurtarmak için sürdürülebilirliği azaltan bir kodlama standardını genelleştirmeye çalışmak çok etkisiz önceliktir.

Ç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 flamalar önemlidir. Bu durumda 10 milyon dolar tasarruf ediyoruz. Sadece vücudun binlerce dolara mal olduğu iki kez denilen bir işleve sahip olduğunuzda, peni tıraş etmeye değmez. Bir araba satın alırken zamanınızı pennies üzerinde pazarlık etmek akıllıca değil. Bir üreticiden bir milyon kutu soda satın alıyorsanız, pennies üzerinde pazarlık etmeye değer. Etkili optimizasyonun anahtarı, bu maliyetleri uygun bağlamlarında anlamaktır. Her satın alımda peni kaydetmeye çalışan ve satın aldıkları şey ne olursa olsun herkesin pennies üzerinde pazarlık yapmaya çalıştığını öneren bir kişi yetenekli bir optimize edici değildir.

3
user204677

İş arkadaşınızın performans konusunda çok endişeli olduğu anlaşılıyor. Bazı durumlarda büyük bir vaka/anahtar yapısı daha hızlı performans gösterebilir, ancak umarım sizler OO sürümü ve anahtar/vaka sürümü üzerinde zamanlama testleri yaparak bir deneme yaparsınız. OO sürümünün daha az kodu vardır ve izlenmesi, anlaşılması ve bakımı daha kolaydır. OO sürümü ilk olarak (bakım/okunabilirlik gerektiği gibi) başlangıçta daha önemli olabilir) ve yalnızca /OO sürümü ciddi performans sorunları içeriyorsa ve bir anahtar/büyük/küçük harf durumu önemli bir iyileştirme yapacaksa gösterilebilir.

Kimsenin bahsetmediği polimorfizmin bir sürdürülebilirlik avantajı, her zaman aynı vaka listesini açıyorsanız, ancak bazen birkaç vaka aynı şekilde ve bazen ele alındığında, kodunuzu kalıtım kullanarak çok daha güzel bir şekilde yapılandırabileceğinizdir. değil

Örneğin. Dog, Cat ve Elephant arasında geçiş yapıyorsanız ve bazen Dog ve Cat aynı duruma sahipse bunları yapabilirsiniz her ikisi de soyut bir sınıftan miras alır DomesticAnimal ve bu işlevi soyut sınıfa yerleştirir.

Ayrıca, birkaç kişinin polimorfizmi kullanmayacağınız yere bir ayrıştırıcı kullandığına şaşırdım. Ağaç benzeri bir ayrıştırıcı için bu kesinlikle yanlış bir yaklaşımdır, ancak Meclis gibi, her satırın biraz bağımsız olduğu bir şey varsa ve hattın geri kalanının nasıl yorumlanması gerektiğini gösteren bir kodla başlarsanız, tamamen polimorfizm kullanırdım ve bir Fabrika. Her sınıf ExtractConstants veya ExtractSymbols gibi işlevleri uygulayabilir. Bu yaklaşımı bir oyuncak BASIC yorumlayıcısı için kullandım.

2
jwg

"Zamanın yaklaşık% 97'sini küçük verimlilikleri unutmalıyız: erken optimizasyon tüm kötülüklerin köküdür"

Donald Knuth

0
thorsten müller

Bu sürdürülebilirlik açısından kötü olmasa bile, performans için daha iyi olacağına inanmıyorum. Sanal bir işlev çağrısı, yalnızca bir ekstra dolaylamadır (bir switch deyimi için en iyi durumla aynıdır), bu nedenle C++ 'da bile performans kabaca eşit olmalıdır. Tüm işlev çağrılarının sanal olduğu C # 'da, her iki sürümde de aynı sanal işlev çağrısı yüküne sahip olduğunuzdan, switch deyimi daha kötü olmalıdır.

0
Dirk Holsopple

Mutlaka kıçından bahsetmiyor. En azından C ve C++ switch ifadeleri tabloları sadece temel işaretçiye erişimi olan bir işlevde dinamik bir gönderme ile görmedim iken atlama tabloları için optimize edilebilir. En azından ikincisi, bir temel işaretçi/referans yoluyla sanal bir işlev çağrısından hangi alt türün kullanıldığını tam olarak anlamak için çok daha çevreleyen bir koda bakan çok daha akıllı bir optimize edici gerektirir.

Bunun da ötesinde, dinamik dağıtım genellikle bir "optimizasyon bariyeri" görevi görür, yani derleyici çoğu zaman satır içi kod yazamaz ve yığın dökülmelerini ve tüm bu fantezi şeyleri en aza indirgemek için kayıtları en iyi şekilde ayıramaz. sanal işlev, satır içi ve tüm optimizasyon sihirini yapmak için temel işaretçiden çağrılacaktır. Optimize edicinin çok akıllı olmasını istediğinizden ve dolaylı işlev çağrılarını optimize etmeye çalıştığınızdan emin değilim, çünkü bu, potansiyel olarak belirli bir çağrı yığınından (foo->f(), bir temel işaretçi aracılığıyla bar->f() çağıran koddan tamamen farklı bir makine kodu oluşturmak zorunda kalacak ve bu işlevi çağıran fonksiyonun iki veya daha fazla kod sürümü üretmesi gerekecek. - Oluşturulan makine kodunun miktarı patlayıcı olabilir - belki de sıcak yürütme yollarında izlenirken kodu anında üreten bir izleme JIT ile o kadar da kötü değildir).

Bununla birlikte, birçok cevap yankılandığından, bir miktar marjinal miktarda daha hızlı bir şekilde ele geçirilse bile switch ifadelerinden oluşan bir bot yükünü tercih etmek kötü bir nedendir. Ayrıca, mikro verimlilikler söz konusu olduğunda, dallanma ve satır içi gibi şeyler bellek erişim kalıpları gibi şeylere kıyasla genellikle oldukça düşük önceliğe sahiptir.

Bu, alışılmadık bir cevapla buraya atladım dedi. switch ifadelerinin bir polimorfik çözüm üzerinde korunabilmesi için bir vaka yapmak istiyorum ve ne zaman, sadece switch.

Bunun en iyi örneği merkezi bir olay işleyicisidir. Bu durumda genellikle olayları işleyecek çok fazla yeriniz yoktur, sadece bir tane (neden "merkezi"). Bu durumlar için, polimorfik bir çözeltinin sağladığı genişletilebilirlikten yararlanamazsınız. Analog switch ifadesini yapacak birçok yer olduğunda bir polimorfik çözelti faydalıdır. Yalnızca bir tane olacağından eminseniz, 15 vakalı bir switch ifadesi, geçersiz kılınmış işlevlere ve bunları örneklemek için bir fabrikaya sahip 15 alt tür tarafından miras alınan bir temel sınıf tasarlamaktan çok daha basit olabilir. sonra tüm sistemdeki bir fonksiyonda kullanılabilir. Bu durumlarda, yeni bir alt tür eklemek, bir işleve case ifadesi eklemekten çok daha sıkıcıdır. Eğer bir şey varsa, genişletilebilirlikten hiçbir şekilde yararlanamayacağınız bu tek tuhaf durumda switch ifadelerinin performansı değil, sürdürülebilirliği için tartışırım.

0
user204677

Meslektaşınız, atlama masaları hakkındaki yorum devam ettiği sürece arka tarafından konuşmuyor. Bununla birlikte, kötü kod yazmayı haklı çıkarmak için kullanmak yanlış olduğu yerdir.

C # derleyicisi, yalnızca birkaç durumda anahtar deyimlerini if ​​/ else dizisine dönüştürür, bu nedenle if/else komutunu kullanmaktan daha hızlı değildir. Derleyici daha büyük anahtar ifadelerini sözlüğe (iş arkadaşınızın atıfta bulunduğu atlama tablosu) dönüştürür. Daha fazla bilgi için lütfen konudaki Yığın Taşması sorusunun bu cevabına bakın .

Büyük bir anahtar deyiminin okunması ve bakımı zordur. "Vaka" ve işlevlerin sözlüğünü okumak çok daha kolaydır. Geçiş bu şekilde gerçekleştiğinden, size ve meslektaşınıza doğrudan sözlükleri kullanmanız önerilir.

0
David Arno