Microsoft, oyun geliştirmeye C # özelliklerini kullanma önerisi midir?


26

Bazen bazı özelliklere ihtiyacınız olduğunu anlıyorum:

public int[] Transitions { get; set; }

veya:

[SerializeField] private int[] m_Transitions;
public int[] Transitions { get { return m_Transitions; } set { m_Transitions = value; } }

Ancak benim izlenimim, oyun geliştirmede, aksi halde bir nedeniniz olmadıkça, her şeyin mümkün olduğunca hızlı olması gerektiğidir. Okuduğum şeylerden aldığım izlenim, Unity 3D'nin özellikler yoluyla satır içi erişimden emin olmadığından, bir özellik kullanmanın ekstra bir işlev çağrısı ile sonuçlanacağı yönünde.

Ortak alanları kullanmamak için Visual Studio'nun önerilerini sürekli görmezden gelme hakkım var mı? ( int Foo { get; private set; }Amaçlanan kullanımın gösterilmesinde bir avantaj olan yerlerde kullandığımızı unutmayın .)

Erken optimizasyonun kötü olduğunu biliyorum ama dediğim gibi, oyun geliştirmede benzer bir çabayı çabuklaştırabildiğinde yavaş bir şey yapmıyorsunuz.


34
Bir şeyin "yavaş" olduğuna inanmadan önce daima bir profiler ile doğrulayın. Bir şeyin "yavaş" olduğu söylendi ve profiler bana yarım saniye kaybetmek için 500.000.000 kez yürütmesi gerektiğini söyledi. Asla bir çerçevede birkaç yüz kereden fazla yürütmeyeceğinden, alakasız olduğuna karar verdim.
Almo

5
Burada bir soyutlamanın biraz var olduğunu ve düşük seviye optimizasyonların daha yaygın şekilde uygulanmasına yardımcı olduğunu buldum. Örneğin, bitişik dizilere kıyasla (örneğin destekleri ArrayList) oldukça yavaş olan çeşitli bağlantılı listeler kullanan çok sayıda C projesi gördüm . Bunun nedeni C'nin jenerik olmadığından ve bu C programcılarının bağlantılı listeleri tekrar tekrar uygulamanın muhtemelen yeniden ArrayListsboyutlandırma algoritması için yapmaktan daha kolay olduğunu bulmalarıdır. İyi bir düşük seviye optimizasyonu ile gelirseniz, onu içine alın!
hegel5000

3
@Almo Aynı mantığı 10.000 farklı yerde meydana gelen işlemlere uyguluyor musunuz? Çünkü bu üye girişi. Mantıksal olarak, optimizasyon sınıfının önemli olmadığına karar vermeden önce performans maliyetini 10K ile çarpmalısınız. Ve 0,5 ms, oyununuzun performansını ne zaman düşürmeyeceğinize dair daha iyi bir kuraldır. Amacınız hala geçerli olsa da, doğru eylemin sizin yaptığınız kadar net olmadığını düşünmüyorum.
piojo

5
2 atınız var. Onlarla yarış.
Direk

@piojo Yapmıyorum. Bazı düşünceler daima bu şeylere girmelidir. Benim durumumda, UI kodundaki döngüler içindi. Bir UI örneği, birkaç döngü, birkaç yineleme. Kayıt için yorumunuzu iptal ettim. :)
Almo

Yanıtlar:


44

Ancak benim izlenimim, oyun geliştirmede, aksi halde bir nedeniniz olmadıkça, her şeyin mümkün olduğunca hızlı olması gerektiğidir.

Şart değil. Tıpkı uygulama yazılımında olduğu gibi, bir oyunda performans açısından kritik olan ve olmayan bir kod var.

Kod kare başına birkaç bin kez uygulanırsa, bu kadar düşük seviyeli bir optimizasyon mantıklı olabilir. (İlk önce gerçekten sık sık aramanız gerekip gerekmediğini kontrol etmek isteyebilirsiniz, ancak en hızlı kod çalıştırmadığınız koddur)

Kod her birkaç saniyede bir uygulanırsa, okunabilirlik ve bakım kolaylığı performanstan çok daha önemlidir.

Unity 3D'nin özellikler üzerinden satır içi erişimden emin olmadığından, bir özellik kullanmanın bir ekstra işlev çağrısı ile sonuçlanacağını okudum.

Stilinizdeki önemsiz özelliklerle int Foo { get; private set; }, derleyicinin özellik paketleyicisini uzağa optimize edeceğinden emin olabilirsiniz. Fakat:

  • Eğer gerçekten bir uygulamanız varsa, o zaman artık emin olamazsınız. Bu uygulama ne kadar karmaşık olursa, derleyicinin bunu satır içi yapması o kadar az olasıdır.
  • Özellikler karmaşıklığı gizleyebilir. Bu iki ucu keskin bir kılıçtır. Bir yandan, kodunuzu daha okunaklı ve değişken hale getirir. Ancak öte yandan, sınıfınızın bir özelliğine erişen başka bir geliştirici, yalnızca tek bir değişkene eriştiklerini düşünebilir ve bu özelliğin gerisinde ne kadar kod sakladığınızı fark etmeyebilir. Bir özellik hesaplamalı olarak pahalı hale gelmeye başladığında, onu açık bir şekilde GetFoo()/ SetFoo(value)yöntemde yeniden biçimlendirmeye meyilliyim : Sahnelerin arkasında daha çok şey olduğunu ima etmek. (ya da başkalarının ipucu alacağından daha da emin olmam gerekirse: CalculateFoo()/ SwitchFoo(value))

Üst düzey bir nesnenin salt okunur olmayan bir sınıf olması koşuluyla, bu alan bir yapı vb. İçindeki bir yapı içinde olsa bile, yapı türü salt okunur olmayan bir genel alandaki bir alana erişmek hızlıdır. alan veya salt okunur olmayan bağımsız değişken. Yapılar, bu koşullar geçerliyse performansı olumsuz yönde etkilemeden oldukça büyük olabilir. Bu deseni kırmak, yapı boyutuyla orantılı olarak yavaşlamaya neden olur.
supercat

5
"o zaman onu açık bir GetFoo () / SetFoo (value) yöntemine yeniden yönlendirme eğilimindeyim:" - İyi nokta, ağır işlemleri özelliklerden yöntemlere taşıma eğilimindeyim.
Mark Rogers 20

Unity 3D derleyicisinin bahsettiğiniz optimizasyonu yaptığını doğrulamanın bir yolu var mı (IL2CPP'de ve Mono'da Android için geliştirilir)?
piojo

9
@piojo Çoğu performans sorusunda olduğu gibi, en basit cevap "sadece yap" dır. Kodunuz hazır durumda - bir alana sahip olmakla mülk sahibi olmak arasındaki farkı test edin. Uygulama detayları, yalnızca iki yaklaşım arasındaki önemli bir farkı gerçekten ölçebildiğiniz zaman araştırmaya değer. Tecrübelerime göre eğer her şeyi mümkün olduğunca hızlı yazarsanız, mevcut yüksek seviyeli optimizasyonları sınırlarsınız ve düşük seviyeli parçaları yarışmaya çalışırsınız ("ağaçlar için orman göremezsiniz"). Örneğin, Updatetamamen atlamak için yollar bulmak Updatehızlı yapmaktan daha fazla zaman kazandıracak .
Luaan

9

Şüphe durumunda, en iyi uygulamaları kullanın.

Şüphelisin.


Bu kolay cevaptı. Gerçeklik elbette daha karmaşıktır.

İlk olarak, oyun programlamanın ultra süper yüksek performanslı programlama olduğu ve her şeyin mümkün olduğu kadar hızlı olması gerektiği efsanesi var. Oyun programlamayı performans bilinçli programlama olarak sınıflandırırım . Geliştiricinin performans kısıtlamalarının farkında olması ve bunların içinde çalışması gerekir.

İkincisi, her şeyi mümkün olduğunca hızlı kılan bir programın hızlı çalışmasını sağlayan şeydir. Gerçekte, darboğazları tanımlamak ve optimize etmek, bir programı hızlı yapan şeydir ve bu, kodun bakımı ve değiştirilmesi kolaysa, yapılması daha kolay / daha ucuz / daha hızlıdır; son derece optimize edilmiş kodun bakımı ve değiştirilmesi zordur. Son derece optimize edilmiş programlar yazmak için, aşırı sık kod optimizasyonunu gerektiği kadar sık ​​ve mümkün olduğunca nadir kullanmanız gerekir.

Üçüncüsü, özelliklerin ve erişimcilerin yararı, değiştirilmelerinin sağlam olması ve böylece kodun korunmasını ve değiştirilmesini kolaylaştırmasıdır; bu, hızlı programlar yazmak için gereken bir şeydir. Birisi değerinizi null olarak ayarlamak istediğinde, bir istisna atmak ister misiniz? Bir ayarlayıcı kullanın. Değer değiştiğinde bildirim göndermek ister misiniz? Bir ayarlayıcı kullanın. Yeni değeri kaydetmek ister misiniz? Bir ayarlayıcı kullanın. Tembel başlatma yapmak ister misiniz? Bir alıcı kullanın.

Ve dördüncüsü, şahsen Microsoft'un en iyi uygulamasını bu örnekte takip etmiyorum. Önemsiz ve kamu alıcıları ve belirleyicileri olan bir mülke ihtiyacım olursa , sadece bir alan kullanırım ve gerektiğinde mülkü değiştiririm. Bununla birlikte, çok nadiren böyle önemsiz bir özelliğe ihtiyacım var - sınır koşullarını kontrol etmek veya ayarlayıcıyı özel yapmak istemek çok daha yaygın.


2

.Net çalışma zamanının basit özellikleri satır içinde sıralaması çok kolaydır ve çoğu zaman yapar. Ancak, bu çizgi normalde profilciler tarafından devre dışı bırakılır, bu nedenle yanıltılması kolaydır ve halka açık bir mülkü halka açık alana dönüştürmenin yazılımı hızlandıracağını düşünmek kolaydır.

Kodunuz yeniden derlenirken derlenmeyecek olan diğer kod tarafından çağrılan kodda, özellikleri kullanmak için iyi bir durum vardır, çünkü bir alandan bir özelliğe geçmek bir değişiklik değişikliğidir. Ancak çoğu kod için, get / set üzerinde bir kesme noktası belirleyebilmenin dışında, ortak bir alana kıyasla basit bir mülk kullanmanın faydalarını hiç görmedim.

10 yıl boyunca profesyonelleri C # programcısı olarak kullanmamın birincil nedeni, diğer programcıları kodlama standardına uymadığımı söylemekten başka bir şey yapmamaktı. Bu iyi bir takas oldu, çünkü bir mülkü kullanmamak için hemen hemen hiç bir sebep yoktu.


2

Yapılar ve açık alanlar, bazı yönetilmeyen API'lerle birlikte çalışabilirliği kolaylaştıracaktır. Genellikle düşük seviye API'nin referans olarak değerlere erişmek istediğini göreceksiniz, bu da performans için iyidir (gereksiz bir kopyadan kaçınıyoruz). Özelliklerin kullanılması bunun önünde bir engeldir ve genellikle sarıcı kitaplıklarının kullanım kolaylığı için ve bazen de güvenlik için kopyalar çıkarması gerekir.

Bu nedenle, özellikleri olmayan ama çıplak alanlara sahip vektör ve matris türlerinde daha iyi performans elde edersiniz.


En iyi uygulamalar vakumda yaratma değildir. Bazı kargo kültlerine rağmen, genel olarak en iyi uygulamalar iyi bir neden için var.

Bu durumda bir çiftimiz var:

  • Bir özellik, istemci kodunu değiştirmeden uygulamayı değiştirmenize izin verir (ikili düzeyde, bir alanı müşteri kodunu kaynak düzeyinde değiştirmeden bir özellik olarak değiştirmek mümkündür, ancak değişiklikten sonra farklı bir şey derleyecektir) . Bu, en baştan bir özellik kullanarak, kendinize ait kodun yalnızca mülkün dahili olarak ne yaptığını değiştirmek için derlenmesi gerekmeyeceği anlamına gelir.

  • Türünüzdeki alanların olası tüm değerleri geçerli durum değilse, bunları değiştiren kodla istemci koduna maruz bırakmak istemezsiniz. Bu nedenle, bazı değer kombinasyonları geçersizse, alanları özel (veya dahili) tutmak istersiniz.

Müşteri kodu söylüyorum. Bu, kendinize ait kod anlamına gelir. Bir kütüphane yapmıyorsanız (veya kütüphane bile oluşturuyorsanız ama halk yerine dahili kullanıyorsanız), genellikle onunla ve iyi bir disiplinle karşılaşabilirsiniz. Bu durumda özellikleri kullanmanın en iyi yolu, kendinizi ayağınızdan vurmanızı engellemektir. Ayrıca, bir alanın tek bir dosyada değişebileceği tüm yerleri görebiliyorsanız, başka bir yerde değiştirilip değiştirilmediği konusunda endişelenmek yerine, kod hakkında karar vermek çok daha kolaydır. Aslında, özellikleri neyin yanlış gittiğini bulduğunuzda kesme noktaları koymak için de iyidir.


Evet, sektörde neler yapıldığını görmenin bir değeri var. Ancak, en iyi uygulamalara karşı çıkmak için bir motivasyonunuz var mı? ya da sadece bir başkasının yaptığı için, en iyi uygulamalara karşı mı çıkıyorsunuz - kodun nedenini zorlaştırıyor musunuz? Ah, bu arada, "diğerleri yapar", bir kargo kültüne nasıl başladığın.


Yani ... oyunun yavaş mı çalışıyor? Ne olabileceğini tahmin etmek yerine, tıkanıklığı bulmak ve düzeltmek için zaman ayırmanız daha iyi. Derleyicinin birçok optimizasyon yapacağına emin olabilirsiniz, bu nedenle, şansınızı yanlış soruna bakıyorsunuzdur.

Kapak tarafında, ne ile başlayacağınıza karar verirseniz, alanlar ve özellikler gibi daha küçük detaylar için endişelenmek yerine, önce hangi algoritmaları ve veri yapılarını merak etmelisiniz.


Sonunda, en iyi uygulamalara karşı çıkarak bir şeyler mi kazanıyorsunuz?

Davanızın detayları için (Unity ve Android için Mono), Unity değerleri referans alıyor mu? Olmazsa, değerleri yine de kopyalar, orada performans artışı olmaz.

Varsa, bu verileri ref ile alan bir API'ye aktarıyorsanız. Alanın herkese açık hale getirilmesi mantıklı geliyor mu yoksa türün doğrudan API'yi arayabilmesini sağlayabiliyor musunuz?

Evet, elbette, çıplak alanları olan yapılar kullanarak yapabileceğiniz optimizasyonlar olabilir. Örneğin, onlara işaretçilerle Span<T> veya benzerleriyle erişirsiniz . Aynı zamanda hafızada kompakt olduklarından ağ üzerinden gönderilmek üzere seri hale getirilmelerini veya kalıcı depolamaya alınmalarını kolaylaştırır (ve evet bunlar kopyadır).

Şimdi, doğru algoritmaları ve yapıları seçtiniz mi, eğer bir darboğaza dönüşürlerse, onu düzeltmenin en iyi yolunun ne olduğuna karar verdiniz ... çıplak alanlara sahip yapılar olabilir. Ne zaman ve ne zaman olacağı konusunda endişelenebileceksiniz. Bu arada, oynamaya değer iyi veya eğlenceli bir oyun yapmak gibi daha önemli konular için endişelenebilirsiniz.


1

... benim izlenimim, oyun geliştirmede, başka türlü bir nedeniniz olmadıkça, her şeyin mümkün olduğunca hızlı olması gerektiğidir.

:

Erken optimizasyonun kötü olduğunu biliyorum ama dediğim gibi, oyun geliştirmede benzer bir çabayı çabuklaştırabildiğinde yavaş bir şey yapmıyorsunuz.

Erken optimizasyonun kötü olduğunu, ancak hikayenin sadece yarısı olduğunu bilmekte haklısınız (aşağıdaki alıntıya bakınız). Oyun geliştirmede bile her şey değil mümkün olduğu kadar hızlı olması gerekmez.

İlk önce çalışmasını sağlayın. Ancak o zaman, ihtiyacı olan bitleri optimize edin. Bu, ilk taslağın dağınık veya gereksiz yere yetersiz olabileceği anlamına gelmez, ancak bu aşamada optimizasyon öncelikli değildir.

Asıl sorun, programcıların yanlış yerlerde ve yanlış zamanlarda verim konusunda endişe duymak için çok fazla zaman harcadıklarıdır ; erken optimizasyon, programlamadaki tüm kötülüklerin (veya en azından çoğunun) köküdür.” Donald Knuth, 1974.


1

Bunun gibi temel şeyler için, C # eniyileyici yine de doğru olacak. Bu konuda endişeleniyorsanız (ve kodunuzun% 1'inden fazlası için endişeleniyorsanız, yanlış yapıyorsunuz ) sizin için bir öznitelik oluşturuldu. Bu özellik [MethodImpl(MethodImplOptions.AggressiveInlining)], bir yöntemin işaretlenme olasılığını büyük ölçüde artırır (özellik alıcıları ve belirleyicileri yöntemdir).


0

Yapı tipi üyelerin alanlardan ziyade özellikler olarak ortaya çıkarılması, özellikle yapıların gerçekte "ilginç nesneler" yerine yapılar olarak ele alındığı durumlarda, düşük performans ve anlambilim sağlar.

.NET'teki bir yapı, birlikte bantlanmış ve bazı amaçlar için bir birim olarak ele alınabilen bir alan koleksiyonudur. .NET Framework, yapıların sınıf nesneleriyle aynı şekilde kullanılmasına izin vermek için tasarlanmıştır ve bunun uygun olabileceği zamanlar vardır.

Yapıları çevreleyen tavsiyelerin birçoğu, programcıların alanları bir araya toplanmış alan koleksiyonları yerine nesneler olarak kullanmak isteyecekleri fikrine dayanır. Öte yandan, bantlanmış bir grup demet gibi davranan bir şeyin olmasının yararlı olabileceği birçok durum vardır. İhtiyacınız olan şey buysa, .NET tavsiyesi hem programcılar hem de derleyiciler için gereksiz çalışmalar ekleyerek, doğrudan yapıları doğrudan kullanmaktan daha kötü performans ve anlambilim sağlar.

Yapıları kullanırken akılda tutulması gereken en büyük ilkeler şunlardır:

  1. Yapıları değerine göre geçirmeyin ya da iade etmeyin ya da gereksiz yere kopyalanmalarına neden olun.

  2. İlke # 1'e uyulursa, büyük yapılar da küçük yapılar kadar iyi performans gösterir.

Eğer Alphablob26 kamu içeren bir yapıdır intaz adında alanları ve özellik alıcı abonun toplamını verir ave balanlar, daha sonra verilen Alphablob[] arr; List<Alphablob> list;kod int foo = arr[0].ab + list[0].ab;alanları okumak gerekir ave bbir arr[0], ama her 26 alanlarını okumak gerekir list[0]bile olacak olsa İkisi hariç hepsini görmezden gelin. Biri alphaBlob, benzer bir yapıyla verimli bir şekilde çalışabilecek genel bir liste benzeri koleksiyona sahip olmak isterse , endekslenen alıcıyı bir yöntemle değiştirmelidir:

delegate ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
actOnItem<TParam>(int index, ActByRef<T, TParam> proc, ref TParam param);

bu daha sonra çağıracaktı proc(ref backingArray[index], ref param);. Eğer biri cümledeki böyle bir koleksiyon Verilen sum = myCollection[0].ab;ile

int result;
myCollection.actOnItem<int>(0, 
  ref (ref alphaBlob item, ref int dest)=>dest = item,
  ref result);

Bu alphaBlobözellik özellik alıcıda kullanılmayacak kısımlarını kopyalamaktan kaçınacaktır . Geçen temsilci, refparametreleri dışındaki hiçbir şeye erişmediğinden, derleyici statik bir temsilci geçebilir.

Ne yazık ki, .NET Framework bu şeyin iyi çalışmasını sağlamak için ihtiyaç duyulan delegelerden herhangi birini tanımlamıyor ve sözdizimi biraz karışıklık yaratıyor. Öte yandan, bu yaklaşım yapıların gereksiz yere kopyalanmasını önlemeyi mümkün kılar ve bu da sırayla depoya erişmenin en etkili yolu olan dizilerde depolanan büyük yapılar üzerinde yerinde eylemler gerçekleştirmeyi pratik hale getirir.


Merhaba, cevabınızı yanlış sayfaya gönderdiniz mi?
piojo

@pojo: Belki de cevabın amacının alanlardan ziyade özelliklerin kullanımının kötü olduğu bir durumu tanımlamak ve bir kişinin alanların performansını ve semantik avantajlarını nasıl elde edebileceğini, yine de erişimi içine çekmeyi tanımlamak olduğunu açıkça belirtmeliydim.
supercat

@piojo: Düzenlemem işleri daha açık hale getiriyor mu?
supercat

"İkisinin de haricinde hepsini görmezden gelmesine rağmen 26 listenin [0] tüm alanlarını okumanız gerekecek." ne? Emin misiniz? MSIL'yi kontrol ettiniz mi? C # neredeyse her zaman sınıf tiplerini referansa göre iletir.
pjc50

@ pjc50: Bir özelliği okumak değere göre bir sonuç verir. Hem dizine alınmış listalıcı abhem de özellik alıcı için ikisi de sıralıysa, bir JITRE'nin yalnızca üyelerin ave bgerekli olduğunu tanıması mümkün olabilir , ancak aslında böyle şeyler yapan herhangi bir JITter sürümünün farkında değilim.
supercat
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.